Triggers API
HTTP API for creating, managing, and inspecting CodeSpar triggers — 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. 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 (scope: triggers:write)
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Free-form label (shown in dashboard + delivery logs) |
event | string | Yes | Event filter (* or a specific event like payment.confirmed) |
webhook_url | string | Yes | HTTPS endpoint. Must be reachable from api.codespar.dev |
server_id | string | No | Restrict to events from one MCP server |
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": "payment.confirmed",
"webhook_url": "https://yourapp.com/api/webhooks/codespar",
"server_id": "asaas"
}'Response -- 201 Created
{
"id": "trg_a1b2c3d4e5f6g7h8",
"org_id": "org_xyz789",
"name": "fulfillment-pipeline",
"event": "payment.confirmed",
"server_id": "asaas",
"webhook_url": "https://yourapp.com/api/webhooks/codespar",
"status": "active",
"signing_enabled": true,
"secret": "whsec_3k2j4h5g6f7d8s9a0p1o2i3u4y5t6r7e",
"created_at": "2026-04-22T14:30:00Z"
}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 all triggers in the resolved project (via x-codespar-project header) or the org's default project.
Auth required: Yes (scope: triggers:read)
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | -- | Filter by active, paused, or error |
event | string | -- | Filter by exact event name |
limit | number | 50 | Results per page (max 100) |
offset | number | 0 | Pagination offset |
Response -- 200 OK
{
"data": [
{
"id": "trg_a1b2c3d4e5f6g7h8",
"name": "fulfillment-pipeline",
"event": "payment.confirmed",
"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
}
],
"total": 1
}secret is never included in list responses.
GET /v1/triggers/:id
Get a single trigger's current state.
Auth required: Yes (scope: triggers:read)
Response -- 200 OK
Same shape as a single entry in the list response. No secret.
PATCH /v1/triggers/:id
Update name, webhook URL, event filter, or status. Any field omitted is left unchanged.
Auth required: Yes (scope: triggers:write)
Request body
| Field | Type | Description |
|---|---|---|
name | string | New label |
event | string | New event filter |
webhook_url | string | New HTTPS endpoint |
status | string | active or paused (error is read-only — set by the system) |
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. In-flight deliveries are not cancelled — they run through retries and land in DLQ, but new events will not fire.
Auth required: Yes (scope: triggers:write)
Response -- 204 No Content
POST /v1/triggers/:id/rotate-secret
Mint a new signing secret. The old secret is invalidated immediately — plan a brief window where your endpoint accepts both (verify against the new secret, fall back to the old if rotation is in-flight, cut over once new traffic dominates).
Auth required: Yes (scope: triggers:write)
Response -- 200 OK
{
"id": "trg_abc123",
"secret": "whsec_NEW_secret_returned_once"
}POST /v1/triggers/:id/test-fire
Push a synthetic event through the real delivery pipeline. Retries, signing, DLQ — everything operates exactly as it would in production. Use this to verify your endpoint ahead of a production release.
Auth required: Yes (scope: triggers:write)
Response -- 202 Accepted
{
"delivery_id": "dlv_test_m3n4o5p6",
"status": "queued",
"fixture_event": "payment.confirmed"
}GET /v1/triggers/:id/deliveries
List recent delivery attempts for a trigger — successes and failures.
Auth required: Yes (scope: triggers:read)
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by succeeded, failed, pending, dead_lettered |
from | string | ISO 8601 timestamp |
to | string | ISO 8601 timestamp |
limit | number | Default 50, max 100 |
Response -- 200 OK
{
"data": [
{
"id": "dlv_m3n4o5p6",
"trigger_id": "trg_abc123",
"event": "payment.confirmed",
"status": "succeeded",
"attempt": 1,
"http_status": 200,
"duration_ms": 142,
"fired_at": "2026-04-22T14:30:00Z"
},
{
"id": "dlv_q7r8s9t0",
"trigger_id": "trg_abc123",
"event": "payment.confirmed",
"status": "dead_lettered",
"attempt": 12,
"http_status": 504,
"last_error": "Upstream timeout",
"fired_at": "2026-04-22T10:15:00Z"
}
],
"total": 2
}GET /v1/triggers/:id/deliveries/:did
Get a single delivery with the full request + response body — useful for DLQ debugging.
Auth required: Yes (scope: triggers:read)
Response -- 200 OK
{
"id": "dlv_m3n4o5p6",
"trigger_id": "trg_abc123",
"event": "payment.confirmed",
"status": "succeeded",
"attempt": 1,
"request": {
"url": "https://yourapp.com/api/webhooks/codespar",
"headers": {
"X-CodeSpar-Signature": "7f9b4c...",
"X-CodeSpar-Delivery-Id": "dlv_m3n4o5p6",
"X-CodeSpar-Event": "payment.confirmed"
},
"body": "{\"type\":\"payment.confirmed\",\"data\":{...}}"
},
"response": {
"status": 200,
"body": "",
"duration_ms": 142
}
}GET /v1/triggers/:id/dlq
Dead-lettered deliveries (exhausted 12 retries without a 2xx). Ordered by fired_at descending.
Auth required: Yes (scope: triggers:read)
Response shape matches /deliveries, filtered to status: "dead_lettered".
POST /v1/triggers/deliveries/:did/redeliver
Operator-driven single redelivery. Fires one fresh attempt through the normal pipeline.
Auth required: Yes (scope: triggers:write)
Response -- 202 Accepted
{
"delivery_id": "dlv_m3n4o5p6",
"status": "queued",
"original_delivery_id": "dlv_m3n4o5p6",
"redelivered_at": "2026-04-22T15:00:00Z"
}Redeliveries reuse the original delivery ID + signature — your idempotency key check should still match.
POST /v1/triggers/retry-pending
Org-scoped operation: drain the retry queue immediately instead of waiting for the next scheduled retry. Useful after fixing a widespread outage.
Auth required: Yes (scope: triggers:write)
Response -- 200 OK
{
"enqueued": 47,
"triggers_affected": 3
}Errors
Common status codes for this surface. See the full Error Reference for the complete list.
| Status | Error code | Description |
|---|---|---|
400 | invalid_request | Missing required field, bad event name, malformed URL |
401 | unauthorized | Invalid or missing API key |
403 | forbidden | API key lacks triggers:read / triggers:write scope |
404 | not_found | Trigger ID does not exist in the resolved project |
409 | webhook_url_conflict | Another active trigger in the same project already uses this URL + event pair |
429 | rate_limited | Too many trigger-management calls; see Retry-After |
500 | internal_error | Retry with exponential backoff |
Next steps
Last updated on