Deposit received
A deposit session received funds. For an open-sized deposit destAmount is absent and paidAmount is whatever the payer actually sent, so credit the account from paidAmount.
Verifying the signature
Every delivery carries two signature headers. Prefer x-hypermid-signature-v2:
x-hypermid-signature-v2: t=1755561600,v1=6f1b...c2
Compute HMAC_SHA256(secret, "<t>.<raw body>") and compare in constant time.
The timestamp is inside the signed string, so a captured delivery cannot be
replayed later with a fresh t — reject anything older than your tolerance
(5 minutes is typical).
During a secret rotation the header carries two v1= values, the second
signed with the previous secret. Accept the delivery if either matches; that
overlap is what stops an in-flight retry from failing the moment you rotate.
x-hypermid-signature is the legacy bare-hex header — HMAC_SHA256(secret, raw body)
with no timestamp binding, current secret only. It is still sent for existing
integrations, but it has no replay protection. Do not build new verifiers on it.
Delivery guarantees
Delivery is at-least-once. Retries mean the same data.id can arrive more
than once, so treat the handler as idempotent: key on data.id and ignore an
id you have already settled.
Respond 2xx promptly. We time out at 10 seconds and count anything else as
a failure worth retrying, so acknowledge first and do slow work afterwards.
Headers
The event name, so you can route without parsing the body.
deposit.completed t=<epoch seconds>,v1=<hex hmac>. Carries a second v1= during secret rotation.
Legacy bare-hex HMAC over the body alone. No replay protection; prefer the v2 header.
Body
Event name. Also sent as the x-hypermid-event header so you can route before parsing.
checkout.completed, checkout.expired, checkout.failed, deposit.completed, deposit.expired, deposit.failed, withdrawal.completed, withdrawal.expired, withdrawal.failed Dispatch time, epoch MILLISECONDS. Note this differs from the t= value in x-hypermid-signature-v2, which is epoch SECONDS — sign with the header's value, not this one.
1755561600000
The completed session. Same shape for all three events.
Present and true only for a delivery sent by the dashboard's "Test" button, or by POST /v1/payments/webhooks/{webhookId}/test. A real payment never carries it. Reject or sandbox these in production so a test click cannot credit an account.
true
Response
Acknowledged. Any 2xx stops retries; anything else — or no response within 10 seconds — is retried, so return this BEFORE doing slow work.
The body is ignored. Returning one is optional.
Optional. Nothing reads it — the status code is the whole acknowledgement.