The nine events
Three lifecycle outcomes for each of the three products:
Registering an endpoint without naming
events subscribes it to all nine.
That is the safe default: an unwanted delivery is visible and ignorable, a
missing one is neither.
Full payload schemas are in the
API reference.
The payload
data.destAmount is absent on an open-sized deposit — there was no target
amount to compare against. Credit the account from paidAmount, and treat a
missing destAmount as “any amount was acceptable” rather than as an error.The session read spells the same thing differently, which has caught people:
it always carries destAmount, using "0" for an open session, and reports
openSized: true alongside. Branch on openSized rather than comparing against
"0" — the sentinel is an artifact of a NOT NULL column, not a contract.Verifying the signature
Every delivery carries two signature headers. Usex-hypermid-signature-v2:
HMAC_SHA256(secret, "<t>.<raw body>") and compare in constant time.
The two timestamps intentionally use different units. The
t= value in
x-hypermid-signature-v2 is Unix seconds and is the value included in the
signed string. The webhook body’s timestamp field is Unix milliseconds
(Date.now() style) and is informational; do not use it as t or divide it
into the signed body before verifying. For example, a header t=1755561600
corresponds to a body timestamp near 1755561600000.The legacy header
x-hypermid-signature is a bare hex HMAC over the body alone, with no
timestamp binding and therefore no replay protection. It is still sent so
existing integrations keep working. Do not build new verifiers on it.
Rotation
Rotating mints a new secret and demotes the current one to a 24-hour overlap window. During that window deliveries carry a secondv1= computed with the
old secret, so a verifier holding either value still succeeds. Update your
stored secret at your leisure inside the window.
Delivery guarantees
At-least-once — make your handler idempotent
At-least-once — make your handler idempotent
Retries mean the same
data.id can arrive more than once. Key on
data.id and ignore an id you have already settled. Doing the work twice
is your bug to prevent, not something the network can guarantee away.Answer within 10 seconds
Answer within 10 seconds
We time out at 10 seconds and count anything that is not a 2xx as a
failure worth retrying. Acknowledge first, do slow work afterwards.
Order is not guaranteed
Order is not guaranteed
Deliveries are independent. Do not infer sequence from arrival order —
use
timestamp, or re-read the session.The endpoint must be publicly routable
The endpoint must be publicly routable
Re-checked at delivery time, not only at registration, so repointing a
registered hostname at an internal address will not reach it.
Environments
An endpoint belongs to the environment of the key that registered it. Register withsk_test_… and it receives sandbox completions only; register with
sk_live_… and it receives production ones. Neither key can list, delete,
rotate or read the secret of the other’s endpoints.
So a sandbox integration is exercised end to end without any risk of a test
delivery reaching the handler that credits real accounts — and without a
sandbox key being able to reach a production signing secret.
Each environment has its own signing secret, because each endpoint does.
Store them separately; verifying a sandbox delivery against your production
secret fails, correctly.
Testing
POST /v1/payments/webhooks/{webhookId}/test sends a realistically-shaped
delivery, as does the Test button in the dashboard.
You can also replay a body at your own receiver from the
Postman collection, which ships the three events as
example requests.