Triggers API
HTTP API for creating, managing, and inspecting CodeSpar triggers, the outbound webhook subscriptions with signed deliveries, retries, and a dead-letter queue.
Triggers API
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
Connections API
HTTP API reference for provider connections — create and rotate credentials, list and inspect connections, revoke, and manage per-connection merchant config.
Servers API
HTTP API reference for browsing and filtering CodeSpar's catalog of MCP servers across payments, fiscal, logistics, messaging, banking, ERP, and crypto.