Skip to main content
API reference

Triggers

Generated HTTP reference for the 10 operations the published OpenAPI document describes under triggers.

9 min read
View MarkdownEdit on GitHub

Triggers

This page is generated from the published OpenAPI document. It is complete with respect to that document and says nothing about surfaces the document does not describe yet. See what is generated here for what that means.

Base URL: https://api.codespar.dev

Every operation below requires a Bearer token. See Authentication.

Base URL: https://api.codespar.dev

See the Triggers concept for the mental model (event catalog, retry semantics, signature verification). This page is the endpoint reference.

All endpoints require authentication via Bearer token and operate on the resolved project. See Authentication.


POST /v1/triggers

Create a new trigger. The signing secret is returned once on this call and on rotate-secret, never in list/get responses.

Auth required: Yes

Request body

FieldTypeRequiredDescription
namestringYesFree-form label (shown in dashboard + delivery logs)
eventstringYesExact event name to subscribe to, e.g. commerce.payment.succeeded. Lowercase, dot-separated. Wildcards are not supported; * is rejected with 400.
webhook_urlstringYesHTTPS endpoint reachable from api.codespar.dev. Private, loopback, and cloud-metadata hosts are rejected.
server_idstringNoAssociate the trigger with one catalog server (must exist in the catalog). Used as a list filter; delivery matching is by event name.

curl example

curl -X POST https://api.codespar.dev/v1/triggers \
  -H "Authorization: Bearer csk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "fulfillment-pipeline",
    "event": "commerce.payment.succeeded",
    "webhook_url": "https://yourapp.com/api/webhooks/codespar",
    "server_id": "stripe"
  }'

Response -- 201 Created

{
  "id": "trg_a1b2c3d4e5f6g7h8",
  "org_id": "org_xyz789",
  "project_id": "prj_abc123",
  "name": "fulfillment-pipeline",
  "event": "commerce.payment.succeeded",
  "server_id": "stripe",
  "webhook_url": "https://yourapp.com/api/webhooks/codespar",
  "status": "active",
  "total_runs": 0,
  "last_run_at": null,
  "created_at": "2026-04-22T14:30:00Z",
  "signing_enabled": true,
  "secret": "8c2f4b0a1d9e6c3b5a7f0e2d4c6b8a9f1e3d5c7b9a0f2e4d6c8b0a1f3e5d7c9b"
}

The secret appears only on this response. Save it immediately to your secret manager (AWS Secrets Manager, Vercel Environment Variables, 1Password, etc.). If lost, use POST /v1/triggers/:id/rotate-secret to mint a new one, which invalidates the old.


GET /v1/triggers

List triggers in the resolved project, newest first, with cursor pagination.

Auth required: Yes

Query parameters

ParameterTypeDefaultDescription
limitnumber50Results per page (max 100)
beforestring--Cursor: a trigger id from the previous page (next_before)
statusstring--Filter by active, paused, or error
eventstring--Filter by exact event name
server_idstring--Filter by associated catalog server

Response -- 200 OK

{
  "triggers": [
    {
      "id": "trg_a1b2c3d4e5f6g7h8",
      "org_id": "org_xyz789",
      "project_id": "prj_abc123",
      "name": "fulfillment-pipeline",
      "event": "commerce.payment.succeeded",
      "server_id": "stripe",
      "webhook_url": "https://yourapp.com/api/webhooks/codespar",
      "status": "active",
      "total_runs": 1847,
      "last_run_at": "2026-04-22T14:30:00Z",
      "created_at": "2026-04-01T09:00:00Z",
      "signing_enabled": true
    }
  ],
  "next_before": null
}

Pass next_before back as before to fetch the next page; it is null on the last page. secret is never included in list responses.


GET /v1/triggers/:id

Get a single trigger's current state.

Auth required: Yes

Response -- 200 OK

Same shape as a single entry in the list response. No secret.


PATCH /v1/triggers/:id

Update name, webhook URL, or status. Any field omitted is left unchanged. The event filter is immutable: create a new trigger to subscribe to a different event.

Auth required: Yes

Request body

FieldTypeDescription
namestringNew label
webhook_urlstringNew HTTPS endpoint
statusstringactive or paused (error is read-only, set by the system on auto-pause)

Use case: resume an auto-paused trigger

curl -X PATCH https://api.codespar.dev/v1/triggers/trg_abc123 \
  -H "Authorization: Bearer csk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'

DELETE /v1/triggers/:id

Hard-delete the trigger. New events stop firing immediately, and pending retries for the trigger are not re-dispatched (the retry worker only claims deliveries whose trigger still exists and is active).

Auth required: Yes

Response -- 204 No Content


POST /v1/triggers/:id/rotate-secret

Mint a new signing secret. The vault entry is overwritten in place, so the old secret stops signing new deliveries immediately; deliveries already signed with it may still arrive for a short window, so verify against the new secret and fall back to the old until traffic cuts over.

Auth required: Yes

Response -- 200 OK

{
  "trigger_id": "trg_abc123",
  "signing_enabled": true,
  "secret": "5d7c9b8c2f4b0a1d9e6c3b5a7f0e2d4c6b8a9f1e3d5c7b9a0f2e4d6c8b0a1f3e"
}

If the secret store is unavailable the endpoint returns 503 vault_unavailable and no rotation occurs.


POST /v1/triggers/:id/test-fire

Push a synthetic event through the real delivery pipeline: signing, retries, DLQ, everything operates exactly as in production, and the attempt shows up in the deliveries list. The trigger must be active. Only the named trigger receives the test fire; sibling triggers subscribed to the same event stay quiet.

Auth required: Yes

Request body (optional)

FieldTypeDescription
event_typestringEvent name to fire, defaults to trigger.test_fire. Set it to rehearse a specific handler, e.g. commerce.payment.succeeded.
payloadobjectExtra keys merged into the fixture { test: true, trigger_id, requested_at }

Response -- 202 Accepted

{
  "trigger_id": "trg_abc123",
  "event_id": "evt_9f8e7d6c5b4a3210",
  "delivery_id": "412",
  "status": "delivered",
  "response_status": 200,
  "error": null
}

status reflects the synchronous first attempt: delivered, or failed (a retry is scheduled on the normal backoff). Test-firing a trigger that is not active returns 409 trigger_not_active.


GET /v1/triggers/:id/deliveries

List recent delivery attempts for a trigger, successes and failures, newest first. Each attempt is its own row.

Auth required: Yes

Query parameters

ParameterTypeDescription
limitnumberDefault 50, max 200

Response -- 200 OK

{
  "deliveries": [
    {
      "id": "412",
      "trigger_id": "trg_abc123",
      "event_id": "evt_9f8e7d6c5b4a3210",
      "attempt": 1,
      "status": "delivered",
      "response_status": 200,
      "error": null,
      "delivered_at": "2026-04-22T14:30:01Z",
      "next_retry_at": null,
      "created_at": "2026-04-22T14:30:00Z"
    },
    {
      "id": "398",
      "trigger_id": "trg_abc123",
      "event_id": "evt_1a2b3c4d5e6f7890",
      "attempt": 5,
      "status": "dead",
      "response_status": 504,
      "error": "http_504",
      "delivered_at": null,
      "next_retry_at": null,
      "created_at": "2026-04-22T10:15:00Z"
    }
  ]
}

Delivery status values: delivered, failed (retry scheduled, see next_retry_at), pending (redelivery waiting for the retry worker), dead (all 5 attempts exhausted).


GET /v1/triggers/:id/deliveries/:did

Get a single delivery with the reconstructed request and captured response, useful for DLQ debugging.

Auth required: Yes

Response -- 200 OK

{
  "delivery": {
    "id": "412",
    "trigger_id": "trg_abc123",
    "event_id": "evt_9f8e7d6c5b4a3210",
    "attempt": 1,
    "status": "delivered",
    "response_status": 200,
    "error": null,
    "delivered_at": "2026-04-22T14:30:01Z",
    "next_retry_at": null,
    "dead_at": null,
    "receipt_at": null,
    "created_at": "2026-04-22T14:30:00Z"
  },
  "request": {
    "url": "https://yourapp.com/api/webhooks/codespar",
    "sent_at_unix": 1745332200,
    "headers": {
      "Content-Type": "application/json",
      "X-CodeSpar-Event": "commerce.payment.succeeded",
      "X-CodeSpar-Event-Id": "evt_9f8e7d6c5b4a3210",
      "X-CodeSpar-Trigger-Id": "trg_abc123",
      "X-CodeSpar-Attempt": "1",
      "X-CodeSpar-Signature": "t=1745332200,v1=<redacted>"
    },
    "body": "{\"id\":\"evt_9f8e7d6c5b4a3210\",\"type\":\"commerce.payment.succeeded\",\"source\":\"stripe\",\"occurred_at\":\"2026-04-22T14:30:00Z\",\"data\":{\"payment_id\":\"pay_123\",\"amount_minor\":14900}}"
  },
  "response": {
    "status": 200,
    "body": "",
    "error": null
  },
  "event": {
    "id": "evt_9f8e7d6c5b4a3210",
    "source": "stripe",
    "event_type": "commerce.payment.succeeded",
    "payload": { "payment_id": "pay_123", "amount_minor": 14900 },
    "received_at": "2026-04-22T14:30:00Z",
    "provider_event_id": "evt_native_123"
  }
}

The request.body is rebuilt with the same serializer used at dispatch time, byte-identical to what your endpoint received, so you can re-verify the HMAC locally using sent_at_unix and your signing secret. The v1 value itself is redacted because recomputing it requires the secret.


GET /v1/triggers/:id/dlq

Dead-lettered deliveries: attempts that exhausted all 5 tries without a 2xx. Ordered by dead_at descending. Accepts the same limit parameter as /deliveries (default 50, max 200).

Auth required: Yes

Response -- 200 OK

{
  "dead_letters": [
    {
      "id": "398",
      "event_id": "evt_1a2b3c4d5e6f7890",
      "attempt": 5,
      "response_status": 504,
      "error": "http_504",
      "dead_at": "2026-04-22T13:00:00Z",
      "created_at": "2026-04-22T12:59:50Z"
    }
  ]
}

POST /v1/triggers/deliveries/:did/redeliver

Operator-driven single redelivery. Inserts a fresh pending delivery for the same event (attempt count restarts at 1); the retry worker dispatches it on its next tick. Also resets the trigger's consecutive-failure streak. It does not reactivate an auto-paused trigger; use PATCH /v1/triggers/:id with status: "active" for that.

Auth required: Yes

Response -- 202 Accepted

{
  "redelivery_id": "431",
  "trigger_id": "trg_abc123",
  "event_id": "evt_1a2b3c4d5e6f7890",
  "trigger_status": "error"
}

The redelivery carries the same X-CodeSpar-Event-Id as the original event, so an idempotency check keyed on the event id still matches. The signature is recomputed with a fresh timestamp.


POST /v1/triggers/retry-pending

Project-scoped operation: re-dispatch every delivery whose retry is due, immediately, instead of waiting for the next retry-worker tick. Useful after fixing a widespread outage. Only active triggers are drained.

Auth required: Yes

Response -- 200 OK

{
  "scanned": 47,
  "redispatched": 47,
  "delivered": 45,
  "failed": 2,
  "dead": 0
}

Errors

Common status codes for this surface. See the full Error Reference for the complete list.

StatusError codeDescription
400invalid_body / invalid_queryMissing required field or malformed value; event names must be lowercase dot-separated (* and other wildcards are rejected)
400unknown_serverserver_id is not in the catalog
400not_https / private_host / reserved_hostwebhook_url is not HTTPS or points at a private, loopback, or cloud-metadata address
401unauthorizedInvalid or missing API key
404not_foundTrigger or delivery ID does not exist in the resolved project
409trigger_not_activeTest-fire on a paused or errored trigger
503vault_unavailableSecret rotation could not be persisted; no rotation occurred
500internal_errorRetry with exponential backoff

Next steps

Every operation, from the spec

Generated from the published OpenAPI document, so it never drifts from what the API actually serves. The section above is written by hand and carries what a schema cannot: the object model, field rules, and the order to call things in.

GET /v1/triggers

List triggers

Query parameters

NameTypeRequiredDescription
beforestringno
eventstringno
limitintegerno
server_idstringno
status"active" | "paused" | "error"no

Responses

StatusBodyDescription
200objectOK
400objectBad Request — the body or query did not match the schema.

Response 200

FieldTypeRequiredDescription
next_beforestring,nullyes
triggersarray of Triggeryes

Example response

{
  "triggers": [
    {
      "id": "trg_0000000000000000",
      "org_id": "org_0000000000000000",
      "name": "Example",
      "event": "string",
      "webhook_url": "https://example.com/hook",
      "status": "string",
      "total_runs": 0,
      "created_at": "2026-01-15T12:00:00.000Z",
      "signing_enabled": true
    }
  ]
}

Example request

curl -X GET https://api.codespar.dev/v1/triggers \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

POST /v1/triggers

Subscribe a webhook to an event

Request body

FieldTypeRequiredDescription
eventstringyes
namestringyes
server_idstringno
webhook_urlstring (uri)yes

Responses

StatusBodyDescription
201TriggerCreatedOK
400objectBad Request — the body or query did not match the schema.

Example request

curl -X POST https://api.codespar.dev/v1/triggers \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "name": "Example",
       "event": "string",
       "server_id": "srv_0000000000000000",
       "webhook_url": "https://example.com/hook"
     }'

POST /v1/triggers/deliveries/{delivery_id}/redeliver

Queue a delivery to be sent again (deprecated path)

Path parameters

NameTypeRequiredDescription
delivery_idstringyesDelivery id as decimal digits, the string form of the bigserial column. Up to 19 digits, which is the width of a signed 64 bit maximum; anything else is refused with 400 invalid_delivery_id before the database is touched.

Responses

StatusBodyDescription
202objectAccepted
400objectdelivery_id is not 1 to 19 decimal digits.
404objectNo delivery with that id whose webhook endpoint belongs to the caller's organization and project.

Response 202

FieldTypeRequiredDescription
event_idstringyes
redelivery_idstringyesId of the NEW pending delivery row, bigint as string.
trigger_idstringyes
trigger_statusstringyesStatus of the endpoint the delivery belongs to, read before the insert. Check it: the drain only claims rows whose endpoint is active, so a redelivery queued against a paused or error endpoint sits there until a patch reactivates it.

Example response

{
  "redelivery_id": "redelivery_0000000000000000",
  "trigger_id": "trg_0000000000000000",
  "event_id": "event_0000000000000000",
  "trigger_status": "string"
}

Example request

curl -X POST https://api.codespar.dev/v1/triggers/deliveries/{delivery_id}/redeliver \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

POST /v1/triggers/retry-pending

Drain the retry queue for this project (deprecated path)

Responses

StatusBodyDescription
200objectOK

Response 200

FieldTypeRequiredDescription
deadintegeryesAttempts that failed on the last allowed attempt and were dead lettered.
deliveredintegeryes
failedintegeryesAttempts that failed and are scheduled for another retry.
redispatchedintegeryes
scannedintegeryesRows the claim actually took, which is also the ceiling on the four counters below.

Example response

{
  "scanned": 0,
  "redispatched": 0,
  "delivered": 0,
  "failed": 0,
  "dead": 0
}

Example request

curl -X POST https://api.codespar.dev/v1/triggers/retry-pending \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/triggers/{id}

Read one webhook endpoint (deprecated path)

Path parameters

NameTypeRequiredDescription
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Responses

StatusBodyDescription
200objectOK
404objectNo webhook endpoint with that id in the caller's organization and project.

Response 200

FieldTypeRequiredDescription
created_atstring (date-time)yes
eventstringyesThe event name this endpoint subscribes to, dot separated lowercase.
idstringyes
last_run_atstring,null (date-time)yesTimestamp of the last DELIVERED attempt, on the same rule as total_runs.
namestringyes
org_idstringyes
project_idstring,nullyesNullable in the row type this response is serialized from; a later migration sets the column NOT NULL, so an endpoint created since then always carries one.
server_idstring,nullyes
signing_enabledbooleanyesWhether a signing secret exists. The secret itself is never read back: the serializer drops the vault reference and reports only this boolean.
statusstringyesactive, paused or error. Left as an open string because the column is text with no CHECK constraint: active and paused are what a patch may set, and error is what the dispatcher writes on its own when an endpoint auto-pauses after enough consecutive dead deliveries.
total_runsintegeryesDeliveries that landed. A failed or dead attempt does not count, which is why this can sit at 0 while the deliveries listing is full of rows.
webhook_urlstringyes

Example response

{
  "id": "obj_0000000000000000",
  "org_id": "org_0000000000000000",
  "name": "Example",
  "event": "string",
  "webhook_url": "https://example.com/hook",
  "status": "string",
  "total_runs": 0,
  "created_at": "2026-01-15T12:00:00.000Z",
  "signing_enabled": true
}

Example request

curl -X GET https://api.codespar.dev/v1/triggers/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

DELETE /v1/triggers/{id}

Delete a webhook endpoint (deprecated path)

Path parameters

NameTypeRequiredDescription
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Responses

StatusBodyDescription
204No Content
404objectNo webhook endpoint with that id in the caller's organization and project.
503objectThe signing secret could not be purged, so nothing was deleted. Retriable.

Example request

curl -X DELETE https://api.codespar.dev/v1/triggers/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/triggers/{id}/deliveries

List recent delivery attempts (deprecated path)

Path parameters

NameTypeRequiredDescription
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Query parameters

NameTypeRequiredDescription
limitintegernoRows to return. Clamped into the range 1 to 200: a larger or smaller value is reduced or raised rather than refused, and a value that does not parse as a number falls back to 50.

Responses

StatusBodyDescription
200objectOK
404objectNo webhook endpoint with that id in the caller's organization and project.

Response 200

FieldTypeRequiredDescription
deliveriesarray of objectyes

Example response

{
  "deliveries": [
    {
      "id": "obj_0000000000000000",
      "trigger_id": "trg_0000000000000000",
      "event_id": "event_0000000000000000",
      "attempt": 0,
      "status": "string",
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}

Example request

curl -X GET https://api.codespar.dev/v1/triggers/{id}/deliveries \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/triggers/{id}/deliveries/{delivery_id}

Inspect one delivery attempt (deprecated path)

Path parameters

NameTypeRequiredDescription
delivery_idstringyesDelivery id as decimal digits, the string form of the bigserial column. Up to 19 digits, which is the width of a signed 64 bit maximum; anything else is refused with 400 invalid_delivery_id before the database is touched.
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Responses

StatusBodyDescription
200objectOK
400objectdelivery_id is not 1 to 19 decimal digits.
404objectEither no such webhook endpoint in the caller's organization and project, or no delivery with that id belonging to it. Both answer the same code, on purpose.

Response 200

FieldTypeRequiredDescription
deliveryobjectyes
eventobjectyes
requestobjectyes
responseobjectyes

Example response

{
  "delivery": {
    "id": "obj_0000000000000000",
    "trigger_id": "trg_0000000000000000",
    "event_id": "event_0000000000000000",
    "attempt": 0,
    "status": "string",
    "created_at": "2026-01-15T12:00:00.000Z"
  },
  "request": {
    "headers": {
      "Content-Type": "string",
      "X-CodeSpar-Event": "string",
      "X-CodeSpar-Event-Id": "xcodesparevent_0000000000000000",
      "X-CodeSpar-Trigger-Id": "xcodespartrigger_0000000000000000",
      "X-CodeSpar-Attempt": "string"
    },
    "body": "string"
  },
  "response": {},
  "event": {
    "id": "obj_0000000000000000",
    "source": "string",
    "event_type": "string",
    "payload": {},
    "received_at": "2026-01-15T12:00:00.000Z"
  }
}

Example request

curl -X GET https://api.codespar.dev/v1/triggers/{id}/deliveries/{delivery_id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/triggers/{id}/dlq

List dead lettered deliveries (deprecated path)

Path parameters

NameTypeRequiredDescription
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Query parameters

NameTypeRequiredDescription
limitintegernoRows to return. Clamped into the range 1 to 200: a larger or smaller value is reduced or raised rather than refused, and a value that does not parse as a number falls back to 50.

Responses

StatusBodyDescription
200objectOK
404objectNo webhook endpoint with that id in the caller's organization and project.

Response 200

FieldTypeRequiredDescription
dead_lettersarray of objectyes

Example response

{
  "dead_letters": [
    {
      "id": "obj_0000000000000000",
      "event_id": "event_0000000000000000",
      "attempt": 0,
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}

Example request

curl -X GET https://api.codespar.dev/v1/triggers/{id}/dlq \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

POST /v1/triggers/{id}/rotate-secret

Mint a new signing secret for a webhook endpoint (deprecated path)

Path parameters

NameTypeRequiredDescription
idstringyesEndpoint id, trg_ followed by a 16 character nanoid, as minted by the create call.

Responses

StatusBodyDescription
200objectOK
404objectNo webhook endpoint with that id in the caller's organization and project.
503objectThe new secret could not be persisted, so no rotation occurred and the previous secret is still in force. Retriable.

Response 200

FieldTypeRequiredDescription
secretstringyesThe new signing secret in plaintext, 32 random bytes as hex, shown EXACTLY ONCE. Later reads expose only signing_enabled, and a lost secret is replaced by rotating again.
signing_enabledtrueyes
trigger_idstringyes

Example response

{
  "trigger_id": "trg_0000000000000000",
  "signing_enabled": true,
  "secret": "string"
}

Example request

curl -X POST https://api.codespar.dev/v1/triggers/{id}/rotate-secret \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
Triggers | CodeSpar