Webhooks
PayLynks POSTs events to your HTTPS endpoint as they happen.
Register
curl -X POST https://paylynks.io/api/v1/webhook_endpoints -H "Authorization: Bearer $PAYLYNKS_SECRET_KEY" \
-H "Content-Type: application/json" -d '{"url": "https://your-app.com/api/paylynks/webhook"}'
The response includes secret (whsec_…) once. Store it as PAYLYNKS_WEBHOOK_SECRET. Or run npx paylynks webhooks add <url>, which does both.
event_types— default["*"], or a list like["payment_intent.succeeded"].GET /api/v1/webhook_endpoints,DELETE /:id,POST /:id/test(sends a signedwebhook_endpoint.testnow and returns the status code).- URLs must be public
https(no localhost or private addresses).
Payload
{ "id": "evt_...", "object": "event", "type": "payment_intent.succeeded", "livemode": false,
"created": 1792540800, "data": { "object": { "id": "pi_...", "amount_total": 2575, "status": "succeeded" } } }
Verify the signature
Header: PayLynks-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your secret>. Reject if the HMAC doesn't match or t is more than 5 minutes off. Use the raw request body.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(body: string, header: string | null, secret: string) {
if (!header) return false;
const p = Object.fromEntries(header.split(",").map((x) => x.split("=")));
const t = Number(p.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
return expected.length === p.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
Delivery
Respond with any 2xx within 8 seconds. Failures are retried after 1, 5 and 30 minutes, 2, 6 and 12 hours, then daily, for about three days. The same event can arrive more than once; use id to de-duplicate.
Event types
payment_intent.created,.succeeded,.processing,.payment_failed(bank payments areprocessinguntil they settle, up to 4 business days)payment_link.createdinvoice.created,.finalized,.sent,.payment_succeeded,.paid,.late_fee_applied,.voidedpayment_request.created,.sent,.paid,.canceled,.expiredsubscription.created,.activated,.payment_succeeded,.payment_failed,.unpaid,.completed,.paused,.resumed,.canceled,.payment_method_updatedrefund.created,refund.failedvoice_session.created,.succeededdonation_page.createdmerchant.created,merchant.onboarding_completed(partners)webhook_endpoint.test