Triggers
Triggers are CodeSpar's outbound webhook subscriptions — your app receives signed HTTP callbacks when asynchronous commerce events settle (payment confirmed, invoice authorized, shipment in transit, message delivered).
Triggers
A trigger is a CodeSpar-managed webhook subscription. You register an endpoint in your app, CodeSpar watches the underlying provider (Stripe, Asaas, Correios, etc.), and when a matching event settles — payment.confirmed, invoice.authorized, shipment.in_transit, message.delivered — CodeSpar sends a signed HTTP POST to your endpoint.
Triggers replace the traditional pattern of wiring up one webhook per provider: one endpoint, normalized payload shape, single signing secret, single retry / dead-letter queue.
When to use a trigger vs session.send / session.execute
| You want to... | Use |
|---|---|
| Agent asks "charge R$150 via Pix" — synchronous result | session.execute / session.send |
| Background job: react to "payment confirmed" N minutes after charge | Trigger |
| Cron-like: reconcile Pix every 5 min | Poll /v1/sessions/:id/logs yourself, or a Trigger on payment.reconciled |
| In-conversation "send the checkout link" | session.execute("codespar_notify", ...) — not a trigger |
Triggers are the async half of the Complete Loop. Whenever an action your agent kicks off today produces a settlement event tomorrow, a trigger is the right primitive.
Lifecycle
- Create via
POST /v1/triggers(or in the dashboard). CodeSpar returns a signing secret once — store it immediately; it is never revealed again. - Event fires somewhere in your session logs (a Pix from
codespar_paysettles, an NF-e fromcodespar_invoiceis authorized). - CodeSpar signs the payload with
HMAC-SHA256(secret, raw_body), puts the hex digest inX-CodeSpar-Signature, andPOSTs to yourwebhook_url. - Your endpoint returns
2xxwithin 10 seconds. Any other response (non-2xx, timeout, network error) is retried with exponential backoff up to 12 attempts. - Dead-letter — after 12 failed attempts the delivery lands in the trigger's DLQ. Inspect or operator-redeliver via the API.
Trigger object
{
"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",
"total_runs": 1847,
"last_run_at": "2026-04-22T14:30:00Z",
"created_at": "2026-04-01T09:00:00Z",
"signing_enabled": true
}| Field | Description |
|---|---|
id | Trigger ID in the form trg_<16chars> |
name | Free-form label shown in the dashboard and logs |
event | Event filter — * matches all, or a specific event name (see Event catalog) |
server_id | Optional — restrict to events originating from one MCP server |
webhook_url | Your HTTPS endpoint. Must be reachable from api.codespar.dev |
status | active, paused, or error (auto-paused after sustained delivery failures) |
signing_enabled | Always true for triggers created through the managed API. Indicates a signing secret exists in the vault. |
The secret field is returned only on POST /v1/triggers (creation) and POST /v1/triggers/:id/rotate-secret (rotation). Never in list or get responses.
Event catalog
The event string is namespaced <domain>.<action>. Events that fire today:
| Event | Fires when |
|---|---|
payment.confirmed | Provider confirmed settlement (Pix, boleto, card capture, SPEI) |
payment.failed | Charge permanently failed after retries |
payment.refunded | Refund settled back to the original payment method |
payout.settled | Outbound payout reached the destination account |
invoice.authorized | NF-e / NFS-e / CFDI authorized by the tax authority |
invoice.rejected | Fiscal authority rejected the document (see reason field in payload) |
shipment.label_created | Shipping label purchased, tracking code available |
shipment.in_transit | Carrier scan — object left origin |
shipment.delivered | Carrier confirmed final delivery |
message.delivered | WhatsApp / SMS / email confirmed by the channel |
message.read | User opened the message (where the channel reports it) |
connection.expired | OAuth token expired — no more tool calls until re-authorized |
Pass event: "*" to receive everything, or one specific event to filter. For multi-event subscriptions, create multiple triggers — they share no state, which keeps retry semantics clean.
Signature verification
Every delivery includes these headers:
POST /api/webhooks/codespar HTTP/1.1
Content-Type: application/json
X-CodeSpar-Signature: 7f9b4c...
X-CodeSpar-Event: payment.confirmed
X-CodeSpar-Delivery-Id: dlv_m3n4o5p6
X-CodeSpar-Timestamp: 2026-04-22T14:30:00ZVerify with HMAC-SHA256(secret, raw_body) and a constant-time compare:
import crypto from "node:crypto";
export async function POST(req: Request) {
const raw = await req.text();
const signature = req.headers.get("X-CodeSpar-Signature");
const expected = crypto
.createHmac("sha256", process.env.CODESPAR_TRIGGER_SECRET!)
.update(raw)
.digest("hex");
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(raw);
// handle event.type + event.data ...
return new Response(null, { status: 200 });
}Always validate the signature against the raw request body, not the parsed JSON. JSON parse / re-serialize reorders keys and breaks the HMAC match.
Retries and dead-lettering
- Retry schedule: 12 attempts over roughly 24 hours with exponential backoff (10s, 30s, 2min, 10min, 30min, 1h, 2h, 4h, 6h, 8h, 10h, 12h).
- Success = your endpoint returns any
2xxwithin 10 seconds of connection. - Auto-pause: 50 consecutive failed deliveries across one trigger flips
statustopaused. Clear the underlying issue thenPATCH /v1/triggers/:idwithstatus: "active"to resume. - DLQ inspection:
GET /v1/triggers/:id/dlqlists every delivery that exhausted retries. The response includes the full request + last response body so you can replay locally. - Operator redeliver:
POST /v1/triggers/deliveries/:did/redeliverre-enqueues a single delivery. Useful for one-off fixes; do not loop it in production.
Idempotency
CodeSpar guarantees at-least-once delivery — the same delivery may arrive more than once under retry or during network partitions. Use X-CodeSpar-Delivery-Id as your idempotency key:
const deliveryId = req.headers.get("X-CodeSpar-Delivery-Id");
if (await alreadyProcessed(deliveryId)) {
return new Response(null, { status: 200 });
}
await processEvent(event);
await markProcessed(deliveryId);Test-fire
POST /v1/triggers/:id/test-fire pushes a synthetic event through the real delivery pipeline (with retries, signing, DLQ — everything). Good for verifying your endpoint ahead of a production release.
curl -X POST https://api.codespar.dev/v1/triggers/trg_abc123/test-fire \
-H "Authorization: Bearer csk_live_..."The payload uses a fixture shape that matches the subscribed event; your endpoint should respond exactly as it would in production.
Next steps
Last updated on