Skip to main content
Two push channels tell your backend what happened, so you don’t have to poll:
  1. Webhooks — one per-merchant URL that receives EVERY payment event.
  2. IPN — an optional per-payment callback (ipnCallbackUrl at creation).
Both are signed.
Always verify the signature, and never credit an order from a push alone. Treat the push as a hint and confirm the state with GET /api/v1/payments/:id before releasing goods.

Webhook configuration

Config lives at /api/v1/me/webhook and requires a dashboard JWT. Roles: merchant_admin / merchant_member (+ webhooks:view / webhooks:manage).
POST /rotate-secret is the only time the 64-hex signing secret is ever visible — store it in your secrets manager the moment it is returned. The old secret stops verifying immediately.

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.
A verified push is still a hint, not proof: confirm the payment state with GET /api/v1/payments/:id before releasing goods.

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 set ipnCallbackUrl 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, and x-liddie-delivery-id.
  • Timestamp units differ from webhooks. IPN signs and sends x-liddie-timestamp in 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 per paymentId:status (with the paid amount appended for partial top-ups), which lets a consumer collapse duplicate deliveries of the same logical event.
Webhooks (one URL, all events, self-service secret) are the recommended channel; use IPN when a specific order needs its own callback target.

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.