Skip to main content

Payment webhooks

When a checkout reaches an outcome, Apiosk Pay posts a signed event to the webhook_url on your marketplace. It fires on paid, failed and cancelled. The destination is frozen onto the checkout at creation, so changing your webhook URL mid-flight does not redirect events for payments already in progress.

Headers

Body

Amounts here are minor units, not dollar strings. merchant_id is the canonical Connect merchant UUID, the same value seller.id returned at creation.

Verifying

The signature is HMAC-SHA256 over the string "{t}.{raw body}", keyed with your Connect webhook secret, hex encoded.
Three things go wrong here in practice:
  • Verify over the raw bytes. Parsing the JSON and re-serialising it reorders keys and changes the signature. Capture the body before your framework touches it.
  • Check t. The timestamp is what makes a captured event unusable later. Ignoring it gives up replay protection entirely.
  • Compare in constant time. A byte-by-byte early return leaks the expected signature over enough attempts.
If the platform has no webhook secret configured, delivery is skipped, not sent unsigned. An unsigned webhook is indistinguishable from a forged one.

Delivery is best-effort

One attempt, a 5 second timeout, no retries. It is a nudge to go and look, and your integration must be correct without it.
Never release goods on the webhook alone. It can be late, lost, replayed or forged. Confirm with GET /v1/payments/{id}, which is the authority.

Not the same as merchant.activated

Connect’s own merchant.activated webhook, fired by the gateway when a seller finishes onboarding, uses the same header names but a bare HMAC of the body with no timestamp, and retries three times. If you verify both in one handler, do not share the verification code: a bare-HMAC verifier will reject every payment event, and a timestamped verifier will reject every activation.