---
title: "Webhooks"
description: "Receive, verify, retry, and rotate signed deposit event deliveries."
canonical_url: "https://checkout.verify.et/docs/webhooks"
markdown_url: "https://checkout.verify.et/docs/webhooks.md"
last_updated: "2026-10-06"
x_farming_labs_generated_preamble: true
---

# Webhooks
URL: /docs/webhooks
LLM index: /llms.txt
Description: Receive, verify, retry, and rotate signed deposit event deliveries.
Related: /docs/reconciliation, /docs/authentication, /docs/troubleshooting

# Receive signed events

Webhooks are recommended automation, not a requirement. Polling remains a
supported fallback and recovery path.

<Callout type="info" title="A webhook is a change signal">
  Save the deposit ID when you create the checkout. When a signed webhook
  arrives, verify and deduplicate it, then retrieve
  `GET /v1/deposits/{depositId}` whenever you need the authoritative current
  state. Fulfill exactly once only when that state is `succeeded`; never fulfill
  from the browser return or delivery order alone.
</Callout>

## Create and activate an endpoint

1. Open [Webhook endpoints](/dashboard/developers/webhooks) and add an HTTPS URL.
2. Select events. Outcome events and `webhook.test` are selected by default;
   lifecycle events are opt-in.
3. Copy the one-time `whsec_…` signing secret.
4. Send a test delivery. The first successful `2xx` response activates the endpoint.

<DashboardActionCard href="/dashboard/developers/webhooks" title="Webhook endpoints" description="Add an endpoint, copy its signing secret, and send the activation test." action="Configure webhooks" />

## Verify every delivery

Read the raw request bytes before JSON parsing. The signature is HMAC-SHA256 over
`<timestamp>.<raw-body>` and uses the current signature version `v1`.

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

function verifyWebhook(rawBody: Buffer, timestamp: string, signature: string, secret: string) {
  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");
  const received = signature.startsWith("v1=") ? signature.slice(3) : "";
  if (received.length !== expected.length) return false;
  return timingSafeEqual(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));
}
```

Required headers:

| Header | Purpose |
| --- | --- |
| `VerifyCheckout-Event` | Event type |
| `VerifyCheckout-Event-Id` | Stable event ID for deduplication |
| `VerifyCheckout-Delivery-Id` | This delivery attempt |
| `VerifyCheckout-Timestamp` | Unix timestamp included in the signature |
| `VerifyCheckout-Signature` | `v1=<hex-hmac-sha256>` signature |
| `VerifyCheckout-Api-Version` | Webhook payload contract version |
| `VerifyCheckout-Test` | Present on test deliveries |

The current webhook payload API version is `2026-06-01`.

Reject stale timestamps before processing, compare signatures in constant time,
and return `2xx` only after the event is durably accepted.

## Events and delivery semantics

Outcome events cover completed, failed, expired, and review results. Optional
lifecycle events describe earlier progress; `webhook.test` verifies connectivity.
The dashboard event selector is the authoritative catalog for your account.

Deliveries are at least once. They can be duplicated, retried after non-`2xx`
responses, and arrive out of order. Deduplicate by event ID and make wallet
crediting idempotent by deposit ID. Do not assume the next event is newer—retrieve
the current deposit when ordering matters.

The recommended end-to-end sequence is: create checkout → save deposit ID →
send the customer to checkout → accept the signed webhook → retrieve the
deposit when confirmation is needed → fulfill only when its current status is
`succeeded`. See [Create a Deposit](/docs/integration) for the complete flow.

Webhook payloads include `environment: "live"` as a compatibility field. The
current platform has one active flow; do not branch business logic on this value.

## Rotate a signing secret

Create or rotate the endpoint secret in the dashboard, deploy the new `whsec_…`
value to your receiver, send a test delivery, and remove the old value after the
transition window. Never use an API key as a webhook secret.

## Sitemap

Sitemap discovery is not enabled for this deployment.
