LidyaDocs

Webhooks

Lidya sends signed HTTPS POST requests to your endpoints when state changes happen. The same payload is delivered to your webhookEndpoint.url and (after the customer redirect completes) appended to your configured returnUrl.

Headers

Every webhook carries:

HeaderPurpose
Lidya-SignatureHMAC-SHA256 of ${timestamp}.${rawBody} using your endpoint's secret. Hex-encoded.
Lidya-TimestampUnix seconds when we signed.
Lidya-Webhook-IdStable id for de-duplication. Stash for 24h.
Lidya-EventEvent type — same as eventType in body.

Verification

You must check three things, in this order:

  1. Timestamp skew — reject if now - timestamp > 5 minutes.
  2. Signature — recompute HMAC-SHA256 with timingSafeEqual.
  3. Replay — if you've seen Lidya-Webhook-Id before, return 200 silently.
import { createHmac, timingSafeEqual } from "node:crypto";
 
const SKEW = 5 * 60;
 
export async function verify(req: Request, secret: string): Promise<boolean> {
  const ts = Number(req.headers.get("Lidya-Timestamp"));
  if (Math.abs(Date.now() / 1000 - ts) > SKEW) return false;
 
  const raw = await req.text();
  const sig = req.headers.get("Lidya-Signature")!;
  const expected = createHmac("sha256", secret)
    .update(`${ts}.`)
    .update(raw)
    .digest("hex");
  return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Event types

EventTrigger
charge.pendingNew charge created.
charge.succeededAcquirer confirmed payment.
charge.failedCustomer attempt declined.
charge.expiredCustomer never paid within window.
charge.refundedYou issued a refund.
payout.queuedPayout enqueued for broadcast.
payout.broadcastedTX hash assigned, awaiting confirmations.
payout.succeededConfirmed on-chain.
payout.failedBroadcast failed.

Retries

Failed deliveries (anything that isn't a 2xx within 10 seconds) are retried with exponential backoff for up to 24 hours. After that we mark the delivery failed and stop retrying. You can fetch missed deliveries from the Events API anytime.

Idempotency

Always treat webhooks as at-least-once. The Lidya-Webhook-Id header is stable across retries — keep a 24-hour set in Redis or your database and ack-without-side-effects on duplicates.