---
title: Webhook Providers Reference
description: Per-provider inbound webhook signature schemes — HMAC-SHA256, ECDSA P-256, HTTP Basic, shared-secret. For self-hosters and operators verifying provider events.
---

<ActionBar githubPath="content/docs/cookbooks/webhook-providers.mdx" markdownPath="/api/docs-md/cookbooks/webhook-providers" />

This page is a reference for **inbound** webhook verification — how each upstream provider authenticates the events it sends to CodeSpar. It is distinct from the [Webhook Listener cookbook](/docs/cookbooks/webhook-listener), which covers **outbound** webhooks (CodeSpar → integrator) signed with `X-CodeSpar-Signature`.

<Callout type="info">
On the **managed tier** you do not need to implement any of this — the backend handles every inbound provider webhook for you at `POST /v1/webhooks/<server_id>/<connection_id>`. This page is for **self-hosters** running the OSS runtime, and for operators who want to understand what is happening per provider.
</Callout>

## The four verification schemes

Across the 14+ providers integrated today, every inbound webhook falls into one of four schemes:

| Scheme | What it is | Verify by |
|---|---|---|
| **HMAC-SHA256** | Provider signs the raw body with a shared secret | `crypto.createHmac("sha256", secret).update(body).digest("hex")` |
| **ECDSA P-256** | Provider signs with an asymmetric key; you verify with their public key | Stripe SDK `stripe.webhooks.constructEvent(body, sig, secret)` |
| **HTTP Basic** | Provider sends `Authorization: Basic <base64(user:pass)>` | Compare against the credential you stamped at connect time |
| **Shared-secret** | Provider sends a static token in a header — compared with `crypto.timingSafeEqual` | Constant-time compare against the secret you stamped |

## Per-provider table

The table below covers the providers wired into CodeSpar today. The "payload concat order" column matters for HMAC schemes where the signed string is not just the raw body — Mercado Pago is the canonical case.

| Provider | Scheme | Header(s) | Payload signed |
|---|---|---|---|
| **Asaas** | HMAC-SHA256 | `asaas-access-token` | Raw request body |
| **Mercado Pago** | HMAC-SHA256 | `x-signature` + `x-request-id` | `id:<webhook_id>;request-id:<request_id>;ts:<ts>;` |
| **Stripe** | ECDSA P-256 | `Stripe-Signature` | `<timestamp>.<raw_body>` |
| **iugu** | HMAC-SHA256 | `X-Hub-Signature` | Raw request body |
| **Stone** | HMAC-SHA256 | `X-Stone-Signature` | Raw request body |
| **EBANX** | HMAC-SHA256 | `X-Ebanx-Signature` | Raw request body |
| **Sift** | HTTP Basic | `Authorization: Basic <...>` | n/a — credential compared, not body |
| **Konduto** | HTTP Basic | `Authorization: Basic <...>` | n/a — credential compared, not body |
| **Persona** | HMAC-SHA256 | `Persona-Signature` (`t=<ts>,v1=<sig>`) | `<timestamp>.<raw_body>` |
| **Truora** | Shared-secret | `Truora-Signature` | n/a — token compared, not body |
| **Coinbase Commerce** | HMAC-SHA256 | `X-CC-Webhook-Signature` | Raw request body |
| **Z-API** | Shared-secret | `Client-Token` | n/a — token compared (companion header pattern) |

<Callout type="warn">
**Always use `crypto.timingSafeEqual`** when comparing the computed HMAC or stored shared-secret against the value the provider sent. A naive `===` comparison is vulnerable to timing attacks. For ECDSA verification on Stripe, use the official Stripe SDK — do not roll your own.
</Callout>

## Mercado Pago payload concat note

Mercado Pago's HMAC scheme is the most-likely-to-trip-self-hosters case. The signed string is **not** the raw body — it is a structured concatenation of fields from the request:

```
id:<webhook_id>;request-id:<request_id>;ts:<ts>;
```

Where:

- `<webhook_id>` is the `data.id` field on the JSON body.
- `<request_id>` is the value of the `x-request-id` header.
- `<ts>` is the timestamp embedded inside the `x-signature` header (parse `ts=...,v1=...`).

Mercado Pago's webhook payload also does **not** include `external_reference`. The backend handles this with `enrichMercadoPagoFromApi` — a synchronous `GET /v1/payments/:id` against the Mercado Pago API to pull `external_reference` before normalizing. See [async settlement → Mercado Pago caveat](/docs/concepts/async-settlement#mercado-pago-caveat).

## Self-hoster note

If you are running the OSS runtime, you handle inbound verification yourself. The mechanics are the same as the canonical handler: receive the request, look up the connection by the URL path (`/<server_id>/<connection_id>`), pull the secret from your vault, run the right verification, and only then enqueue the event for normalization.

The managed tier does this for you, including the Mercado Pago `external_reference` GET-back, the Stripe SDK verification, and the Persona timestamp-replay window.

## Where to find more

- **Canonical handler** — the managed-tier backend has a single dispatch route per provider, with per-provider verifier modules. Self-hosters can mirror this pattern with the OSS runtime.
- **Outbound webhook reference** — [`/docs/cookbooks/webhook-listener`](/docs/cookbooks/webhook-listener) covers the OTHER direction: CodeSpar → your endpoint, signed with `X-CodeSpar-Signature`.
- **Async settlement** — [`/docs/concepts/async-settlement`](/docs/concepts/async-settlement) explains how webhook events correlate to `tool_call_id` via `idempotency_key` ↔ `external_reference`.

## Next steps

<NextStepsGrid items={[
  { label: "COOKBOOK", title: "Webhook Listener (outbound)", description: "How CodeSpar signs the webhooks IT sends, and how to verify them.", href: "/docs/cookbooks/webhook-listener" },
  { label: "CONCEPT", title: "Async settlement", description: "Why webhooks matter — correlation chain + per-provider idempotency.", href: "/docs/concepts/async-settlement" },
  { label: "CONCEPT", title: "Triggers", description: "Subscribe to normalized events at the org level instead of per-tool-call.", href: "/docs/concepts/triggers" },
]} />
