callback_url — or
callback_url_test for test-mode traffic — as a signed POST.
Event shape
Verify before you trust
Verify the signature before doing anything with the body. A request that fails verification should get a non-2xx response — do not process it and do not acknowledge it. Use your outbound signing secret here. It is a different value from the secret you sign requests with.verify-webhook.ts
The outbound signature always signs the literal string
"outbound" in the
key_id position, not a generation identifier. During a rotation of your
outbound secret you cannot tell from the signed bytes which generation
signed a given callback, so be prepared to verify against either the
previous or the current secret for a short window around a rotation.Correlate on metadata, never on our reference
reference is AptaPay’s own identifier, formatted APT-{prefix}-{nanoid}. It
is globally unique and unguessable, and it deliberately carries none of your
structure. You cannot recover an order id from it, and parsing it as though
it were yours will fail on every callback.
Send what you need at charge time, and read it straight back:
correlate.ts
metadata is stored verbatim on the transaction and echoed on every
delivery. It must serialize to at most 4096 bytes — it rides every
attempt of every event, so an oversized object is rejected with a 422 at
charge time rather than producing undeliverable webhooks. It is null when
the charge carried none.
Delivery is at-least-once and unordered
1
Deduplicate on event_id
AptaPay guarantees a stable
event_id per logical event. Track processed
ids — even a short-TTL cache is enough — so a legitimate retry of an
already-processed event is a safe no-op rather than a double-application.2
Detect out-of-order arrivals with sequence
sequence increases monotonically per transaction. Combined with
previous_status, it lets you ignore a stale delivery rather than
assuming arrival order.3
Check the amount, not just the status
Always compare
observed_amount_minor and observed_currency against
what you expected. A webhook reporting successful for the wrong amount
is the underpayment case. AptaPay already guards this at the gateway — a
mismatch is frozen for review and never fanned out — but your settlement
logic should verify independently rather than trust status alone.Retry schedule
If your endpoint does not return 2xx, AptaPay retries at 0s, 30s, 2m, 10m, 1h, 6h. After the final attempt the event lands in a dead-letter queue with an operator replay path. Return2xx as soon as you have durably recorded the event. Do the slow work
afterwards — a handler that does settlement inline and times out will be
retried, and you will process it twice.
Responding
handler.ts