Skip to main content
Webhooks let your integration react in real time to events that happen on your merchant account (a payment succeeding, an invoice getting paid) — without polling the API. You register one or more webhook endpoints, each subscribed to a specific set of event types. When a matching event occurs, we send an HTTP POST request with the event payload to your endpoint.
There is no “subscribe to everything” option. Every endpoint must list the specific event types it wants — this keeps your integration explicit about what it depends on.

Registering a webhook endpoint

Request

string
required
The HTTPS URL we’ll deliver events to. Must use https:// — plain http:// endpoints are rejected. The destination must also resolve to a public IP address; private, loopback, and link-local addresses are refused when we attempt delivery.
string
An optional label for the endpoint (1–100 characters), useful when you have more than one.
string[]
required
One to 50 event type strings to subscribe to. See Event types below for the full list. Requests referencing an unrecognized or ineligible event type are rejected with a 400.
All mutating requests (create, update, delete, rotate secret) require an Idempotency-Key header, consistent with the rest of the API. Retried requests with the same key return the original result rather than creating a duplicate.

Response

string
Unique identifier for the endpoint, e.g. whk_2sB....
string
string
string
string
One of active, disabled, deleted.
string[]
string
The secret used to sign deliveries to this endpoint, e.g. whsec_....
This is the only time the signing secret is returned in plaintext — on creation, and again if you rotate it. Store it securely; it can’t be retrieved again afterward. If you lose it, rotate the secret to get a new one.
string
string
Response

Event types

Only the event types below can be subscribed to today. Requesting any other event type returns a 400.
More event types are planned. If there’s a specific one you need, let us know.

Delivery format

Every delivery is an HTTP POST with a JSON body shaped like this:
Along with these headers on every request:
Delivery order is best-effort and not guaranteed across events, even for the same resource. Use the id and occurredAt/timestamp fields in the payload to reconcile state rather than assuming events arrive in order.

Verifying signatures

Each delivery is signed with the endpoint’s signing secret so you can verify it genuinely came from us and wasn’t tampered with in transit. The signature header looks like:
To verify it:
  1. Take the raw, unparsed request body bytes — don’t re-serialize a parsed JSON object, since re-serialization can change byte-for-byte formatting and break the signature check. 2. Build the signed content string: {Merchant-Webhook-Id}.{Merchant-Webhook-Timestamp}.{raw body} 3. Compute HMAC-SHA256 of that string using your endpoint’s signing secret, hex-encoded. 4. Compare it (using a constant-time comparison) against the value after v1= in the Merchant-Webhook-Signature header. 5. Reject the request if the timestamp is more than 5 minutes old, to guard against replay attacks.
Given: - Secret: whsec_test_secret - Merchant-Webhook-Id: whkdel_test123 - Merchant-Webhook-Timestamp: 1700000000 - Raw body: {"id":"whkdel_test123","type":"payments.PaymentCreated"}The signed content is:
Which produces:
Use this to sanity-check your own implementation before going live.

Retries and delivery status

Your endpoint should respond with a 2xx status code as quickly as possible. Do your event processing asynchronously and return a 200 immediately — we treat any non-2xx response, a timeout, or a connection error as a failed attempt.
  • Timeout: we wait up to 10 seconds for a response. - Retries: up to 5 attempts total, with a fixed 60-second delay between attempts (so the full retry window is a few minutes, not hours or days). - After the final attempt fails, the delivery is marked exhausted and is not retried automatically. We don’t currently auto-disable an endpoint after repeated failures — check your delivery history periodically if you want to catch a persistently failing endpoint.
Each delivery has one of these statuses:

Viewing delivery history

Supports status, limit, and cursor query parameters. Each delivery includes its attempt history (attemptNumber, attemptedAt, responseStatusCode, durationMs, errorMessage) — useful for debugging why a delivery failed.

Manually redelivering

This creates a new, independent delivery (linked back to the original via redeliveredFromId) — it doesn’t retry or mutate the original.

Managing endpoints

Updating an endpoint lets you change its url, name, eventTypes, or status (active or disabled — disabling pauses deliveries without deleting the endpoint). There’s no way to fetch a single endpoint by ID directly; use the list endpoint and filter client-side.
Rotating the signing secret immediately invalidates the previous secret for signature verification. Plan for a brief cutover window in your integration if you rotate secrets on a live endpoint.

Authentication

All webhook management endpoints use the same authentication as the rest of the Merchant API — send your API key as a bearer token. No separate credential is needed to manage webhook endpoints.