Skip to main content

Webhook Providers Reference

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

1 min read · updated
View MarkdownEdit on GitHub

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, which covers outbound webhooks (CodeSpar → integrator) signed with X-CodeSpar-Signature.

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.

The four verification schemes

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

SchemeWhat it isVerify by
HMAC-SHA256Provider signs the raw body with a shared secretcrypto.createHmac("sha256", secret).update(body).digest("hex")
ECDSA P-256Provider signs with an asymmetric key; you verify with their public keyStripe SDK stripe.webhooks.constructEvent(body, sig, secret)
HTTP BasicProvider sends Authorization: Basic <base64(user:pass)>Compare against the credential you stamped at connect time
Shared-secretProvider sends a static token in a header — compared with crypto.timingSafeEqualConstant-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.

ProviderSchemeHeader(s)Payload signed
AsaasHMAC-SHA256asaas-access-tokenRaw request body
Mercado PagoHMAC-SHA256x-signature + x-request-idid:<webhook_id>;request-id:<request_id>;ts:<ts>;
StripeECDSA P-256Stripe-Signature<timestamp>.<raw_body>
iuguHMAC-SHA256X-Hub-SignatureRaw request body
StoneHMAC-SHA256X-Stone-SignatureRaw request body
EBANXHMAC-SHA256X-Ebanx-SignatureRaw request body
SiftHTTP BasicAuthorization: Basic <...>n/a — credential compared, not body
KondutoHTTP BasicAuthorization: Basic <...>n/a — credential compared, not body
PersonaHMAC-SHA256Persona-Signature (t=<ts>,v1=<sig>)<timestamp>.<raw_body>
TruoraShared-secretTruora-Signaturen/a — token compared, not body
Coinbase CommerceHMAC-SHA256X-CC-Webhook-SignatureRaw request body
Z-APIShared-secretClient-Tokenn/a — token compared (companion header pattern)

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.

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.

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 handlerpackages/api/src/routes/provider-webhooks.ts in codespar-enterprise. Single dispatch point for all providers; per-provider verifier modules under packages/api/src/webhooks/<provider>.ts.
  • Outbound webhook reference/docs/cookbooks/webhook-listener covers the OTHER direction: CodeSpar → your endpoint, signed with X-CodeSpar-Signature.
  • Async settlement/docs/concepts/async-settlement explains how webhook events correlate to tool_call_id via idempotency_keyexternal_reference.

Next steps

Edit on GitHub

Last updated on