Skip to main content
Concepts

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).

3 min read · updated

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 resultsession.execute / session.send
Background job: react to "payment confirmed" N minutes after chargeTrigger
Cron-like: reconcile Pix every 5 minPoll /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

  1. Create via POST /v1/triggers (or in the dashboard). CodeSpar returns a signing secret once — store it immediately; it is never revealed again.
  2. Event fires somewhere in your session logs (a Pix from codespar_pay settles, an NF-e from codespar_invoice is authorized).
  3. CodeSpar signs the payload with HMAC-SHA256(secret, raw_body), puts the hex digest in X-CodeSpar-Signature, and POSTs to your webhook_url.
  4. Your endpoint returns 2xx within 10 seconds. Any other response (non-2xx, timeout, network error) is retried with exponential backoff up to 12 attempts.
  5. 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
}
FieldDescription
idTrigger ID in the form trg_<16chars>
nameFree-form label shown in the dashboard and logs
eventEvent filter — * matches all, or a specific event name (see Event catalog)
server_idOptional — restrict to events originating from one MCP server
webhook_urlYour HTTPS endpoint. Must be reachable from api.codespar.dev
statusactive, paused, or error (auto-paused after sustained delivery failures)
signing_enabledAlways 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:

EventFires when
payment.confirmedProvider confirmed settlement (Pix, boleto, card capture, SPEI)
payment.failedCharge permanently failed after retries
payment.refundedRefund settled back to the original payment method
payout.settledOutbound payout reached the destination account
invoice.authorizedNF-e / NFS-e / CFDI authorized by the tax authority
invoice.rejectedFiscal authority rejected the document (see reason field in payload)
shipment.label_createdShipping label purchased, tracking code available
shipment.in_transitCarrier scan — object left origin
shipment.deliveredCarrier confirmed final delivery
message.deliveredWhatsApp / SMS / email confirmed by the channel
message.readUser opened the message (where the channel reports it)
connection.expiredOAuth 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:00Z

Verify 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 2xx within 10 seconds of connection.
  • Auto-pause: 50 consecutive failed deliveries across one trigger flips status to paused. Clear the underlying issue then PATCH /v1/triggers/:id with status: "active" to resume.
  • DLQ inspection: GET /v1/triggers/:id/dlq lists 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/redeliver re-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