- Webhooks — one per-merchant URL that receives EVERY payment event.
- IPN — an optional per-payment callback (
ipnCallbackUrlat creation).
Webhook configuration
Config lives at/api/v1/me/webhook and requires a dashboard JWT. Roles: merchant_admin / merchant_member (+ webhooks:view / webhooks:manage).
Webhook delivery format
Each event is POSTed to your URL with these headers:
Verification recipe — do all four:
1
Reject stale timestamps
Reject the delivery if
|now − x-liddie-timestamp| > 300 seconds (the replay window).2
Verify the signature
Compute
HMAC-SHA256(yourSecret, timestamp + "." + rawBody) over the raw body bytes (do not re-serialize JSON) and compare constant-time with x-liddie-sig.Verify signature
3
Dedupe on the delivery id
Dedupe on
x-liddie-delivery-id — retries and replays of the same logical event reuse it.4
Acknowledge fast, work async
Respond
2xx fast (under 10 s); do slow work async. Non-2xx triggers automatic retries with backoff.Event catalog
Every event other than
payment.partially_paid fires at most once per payment (deduped per payment + event); dedupe on x-liddie-delivery-id regardless. Payloads carry the payment’s public fields plus your metadata echoed verbatim.
IPN (per-payment callback)
If you setipnCallbackUrl when creating a payment, terminal and major transitions of THAT payment are also POSTed there. The same SSRF/HTTPS rules as webhooks apply.
The IPN signature (v2) differs from the webhook channel:
- Headers:
x-liddie-sig,x-liddie-timestamp, andx-liddie-delivery-id. - Timestamp units differ from webhooks. IPN signs and sends
x-liddie-timestampin Unix milliseconds (Date.now()), whereas the webhook channel uses Unix seconds. Reusing the webhook staleness check (|now_seconds − ts| > 300) on IPN compares seconds against milliseconds and rejects every delivery. For IPN, stay in milliseconds: reject when|Date.now() − Number(x-liddie-timestamp)| > 300000(a 300-second window). - Scheme: HMAC-SHA512 with a per-merchant derived key over the timestamped, canonically key-sorted JSON body:
sig = HMAC-SHA512( HMAC-SHA512(IPN_HMAC_SECRET, merchantId), "<ts>.<body>" ), where<ts>is that millisecond value. - The platform operator gives you your per-merchant IPN key (
HMAC-SHA512(IPN_HMAC_SECRET, merchantId)) — you verify with that key; you never hold the platform-wide secret. - IPN is at-least-once. The same transition can be delivered more than once, so dedupe on
x-liddie-delivery-id. It is stable perpaymentId:status(with the paid amount appended for partial top-ups), which lets a consumer collapse duplicate deliveries of the same logical event.
What you cannot do
- Read the current webhook secret (rotate to get a new one).
- Configure webhooks/IPN keys with an API key (dashboard only).
- Receive events for another merchant (deliveries are tenant-scoped end to end).
Get webhook config
Read your current webhook URL and whether a secret is set.
Rotate webhook secret
Mint the 64-hex signing secret used by the verification recipe.
Get a payment
The source of truth to re-check state after any push.
API Overview
Credentials, response envelopes, and rate limits.