Triggers
Generated HTTP reference for the 10 operations the published OpenAPI document describes under triggers.
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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Free-form label (shown in dashboard + delivery logs) |
event | string | Yes | Exact event name to subscribe to, e.g. commerce.payment.succeeded. Lowercase, dot-separated. Wildcards are not supported; * is rejected with 400. |
webhook_url | string | Yes | HTTPS endpoint reachable from api.codespar.dev. Private, loopback, and cloud-metadata hosts are rejected. |
server_id | string | No | Associate 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
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (max 100) |
before | string | -- | Cursor: a trigger id from the previous page (next_before) |
status | string | -- | Filter by active, paused, or error |
event | string | -- | Filter by exact event name |
server_id | string | -- | 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
| Field | Type | Description |
|---|---|---|
name | string | New label |
webhook_url | string | New HTTPS endpoint |
status | string | active 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)
| Field | Type | Description |
|---|---|---|
event_type | string | Event name to fire, defaults to trigger.test_fire. Set it to rehearse a specific handler, e.g. commerce.payment.succeeded. |
payload | object | Extra 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
| Parameter | Type | Description |
|---|---|---|
limit | number | Default 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.
| Status | Error code | Description |
|---|---|---|
400 | invalid_body / invalid_query | Missing required field or malformed value; event names must be lowercase dot-separated (* and other wildcards are rejected) |
400 | unknown_server | server_id is not in the catalog |
400 | not_https / private_host / reserved_host | webhook_url is not HTTPS or points at a private, loopback, or cloud-metadata address |
401 | unauthorized | Invalid or missing API key |
404 | not_found | Trigger or delivery ID does not exist in the resolved project |
409 | trigger_not_active | Test-fire on a paused or errored trigger |
503 | vault_unavailable | Secret rotation could not be persisted; no rotation occurred |
500 | internal_error | Retry 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
| Name | Type | Required | Description |
|---|---|---|---|
before | string | no | — |
event | string | no | — |
limit | integer | no | — |
server_id | string | no | — |
status | "active" | "paused" | "error" | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Bad Request — the body or query did not match the schema. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
next_before | string,null | yes | — |
triggers | array of Trigger | yes | — |
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
| Field | Type | Required | Description |
|---|---|---|---|
event | string | yes | — |
name | string | yes | — |
server_id | string | no | — |
webhook_url | string (uri) | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | TriggerCreated | OK |
400 | object | Bad 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
| Name | Type | Required | Description |
|---|---|---|---|
delivery_id | string | yes | Delivery 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
| Status | Body | Description |
|---|---|---|
202 | object | Accepted |
400 | object | delivery_id is not 1 to 19 decimal digits. |
404 | object | No delivery with that id whose webhook endpoint belongs to the caller's organization and project. |
Response 202
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | yes | — |
redelivery_id | string | yes | Id of the NEW pending delivery row, bigint as string. |
trigger_id | string | yes | — |
trigger_status | string | yes | Status 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
| Status | Body | Description |
|---|---|---|
200 | object | OK |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
dead | integer | yes | Attempts that failed on the last allowed attempt and were dead lettered. |
delivered | integer | yes | — |
failed | integer | yes | Attempts that failed and are scheduled for another retry. |
redispatched | integer | yes | — |
scanned | integer | yes | Rows 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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No webhook endpoint with that id in the caller's organization and project. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
created_at | string (date-time) | yes | — |
event | string | yes | The event name this endpoint subscribes to, dot separated lowercase. |
id | string | yes | — |
last_run_at | string,null (date-time) | yes | Timestamp of the last DELIVERED attempt, on the same rule as total_runs. |
name | string | yes | — |
org_id | string | yes | — |
project_id | string,null | yes | Nullable 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_id | string,null | yes | — |
signing_enabled | boolean | yes | Whether a signing secret exists. The secret itself is never read back: the serializer drops the vault reference and reports only this boolean. |
status | string | yes | active, 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_runs | integer | yes | Deliveries 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_url | string | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Responses
| Status | Body | Description |
|---|---|---|
204 | — | No Content |
404 | object | No webhook endpoint with that id in the caller's organization and project. |
503 | object | The 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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Rows 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
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No webhook endpoint with that id in the caller's organization and project. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
deliveries | array of object | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
delivery_id | string | yes | Delivery 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. |
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | delivery_id is not 1 to 19 decimal digits. |
404 | object | Either 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
| Field | Type | Required | Description |
|---|---|---|---|
delivery | object | yes | — |
event | object | yes | — |
request | object | yes | — |
response | object | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Rows 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
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No webhook endpoint with that id in the caller's organization and project. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
dead_letters | array of object | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | Endpoint id, trg_ followed by a 16 character nanoid, as minted by the create call. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No webhook endpoint with that id in the caller's organization and project. |
503 | object | The new secret could not be persisted, so no rotation occurred and the previous secret is still in force. Retriable. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | yes | The 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_enabled | true | yes | — |
trigger_id | string | yes | — |
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"