---
title: Webhooks
description: "CodeSpar's webhooks are called Triggers: signed HTTP callbacks (X-CodeSpar-Signature, HMAC-SHA256) delivered when payments settle, fail, or refund. This page maps the name you searched for to the primitive."
---

# Webhooks

You searched for webhooks; in CodeSpar the primitive is called a **[Trigger](/docs/concepts/triggers)**. Same thing, one difference in shape: instead of wiring one webhook per payment provider, you register **one endpoint** and CodeSpar normalizes every provider's callback into a single signed envelope, with one signing secret, one retry policy, and one dead-letter queue.

## The 60-second version

1. **Create a trigger** with `POST /v1/triggers`, naming the event and your HTTPS endpoint. The response carries the signing `secret` exactly once.
2. **An event settles** on any provider connection in your project: `commerce.payment.succeeded`, `commerce.payment.failed`, `commerce.payment.refunded`, `commerce.payment.pending`, `commerce.payment.updated`, `commerce.payment.disputed`.
3. **CodeSpar signs and delivers**: `X-CodeSpar-Signature: t=<unix>,v1=<hex>` where `v1 = HMAC-SHA256(secret, "<timestamp>.<raw body>")`.
4. **Your endpoint answers `2xx` within 10 seconds**; anything else is retried, and after the fifth failure the delivery lands in the trigger's DLQ for inspection or redelivery.

## Verifying the signature

```ts
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
```

Reject deliveries whose timestamp is outside your tolerance window before comparing, and always verify against the **raw** request body, not a re-serialized parse.

## Push or pull: pick per flow

| You need | Use |
|---|---|
| "Tell my backend the moment a Pix charge settles" | A trigger on `commerce.payment.succeeded` |
| A synchronous answer inside an agent conversation | `session.execute` and read the result |
| A status check on one payment you just created | `paymentStatus` / `paymentStatusStream` in the SDK |

Payment confirmation does **not** require polling: triggers are the push path, and they are the right primitive for billing flows that react to settlement.

## Read next

- [Triggers](/docs/concepts/triggers): the full object, event catalog, retries, DLQ, secret rotation.
- [Webhook Listener cookbook](/docs/cookbooks/webhook-listener): a deterministic loop that issues the NF-e, creates shipping, and sends the WhatsApp message on `commerce.payment.succeeded`.
- [Refunds](/docs/concepts/refunds): the outbound half, and the `commerce.payment.refunded` event.
