Developers
Payments on your site, without the payments code
Send customers to a hosted checkout link, get a signed webhook when the money moves, and match it to your records with a passthrough id. No SDK, no card data on your servers, no PCI scope.
How an integration works
- Create a checkout link in the app (Checkout Links → New) or via the API — one-time or subscription billing, with an optional after-payment redirect back to your site.
- Link or redirect your customer to it, appending
?ref=with your own id for the customer or order. - Receive a signed webhook when the payment succeeds (and when subscriptions start, renew, fail, or are canceled) with your
refechoed back — flip the switch in your system there.
Checkout link URL parameters
Every checkout link accepts three optional query parameters:
| Param | What it does |
|---|---|
ref | Your passthrough id (up to 200 characters) — stored with the payment and subscription, returned as reference in webhook payloads, and echoed onto the success redirect. Opaque to Livery. |
email | Prefills the payer's email (still editable). |
name | Prefills the payer's name (still editable). |
https://paywithlivery.com/l/pro-plan ?ref=cus_8fj2k1 # your id — echoed in every webhook &[email protected] # prefills the payer's email &name=Jane%20Doe # prefills the payer's name
If the link has an after-payment redirect configured, the customer is sent there once the charge succeeds, with ?ref= appended when one was given. Treat that redirect as a hint only — the webhook is the source of truth that money moved.
Webhooks
Register an HTTPS endpoint under Settings → Developers. Livery POSTs JSON as events happen; failed deliveries retry after 5 minutes, 30 minutes, 2 hours, and 12 hours. Respond with any 2xx status within 10 seconds. Every event uses the same envelope:
{
"id": "evt_meoq31vhxk2a9",
"type": "payment.succeeded",
"created": 1755388800,
"data": { ... }
}| Event | Fires when |
|---|---|
payment.succeeded | A payment lands — checkout link, invoice, or autopay. |
payment.failed | A charge is declined or errors at the processor. |
payment.refunded | A full or partial refund is issued. |
invoice.paid | An invoice reaches fully paid. |
subscription.created | A subscription checkout link's first payment succeeds and the recurring plan starts. |
subscription.canceled | A recurring plan is canceled. |
dispute.created | A customer opens a chargeback. |
test.ping | You press “Send test” on an endpoint in Settings → Developers. |
Verifying signatures
Every delivery carries a Livery-Signature header — t=<unix seconds>,v1=<hex hmac> — signed with the endpoint's whsec_… secret (shown in Settings → Developers). Recompute the HMAC-SHA256 of `${t}.${rawBody}` and compare in constant time:
import { createHmac, timingSafeEqual } from "crypto";
// header: the Livery-Signature request header
// rawBody: the request body EXACTLY as received (before JSON.parse)
function verifyLivery(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // stale
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Use the Send test button on your endpoint to fire a test.ping through the real signing and delivery pipeline before any money moves.
Sample payloads
payment.succeeded — fired for every successful payment. For a subscription checkout, subscription_id is set on the first charge and every renewal after it:
{
"id": "evt_meoq31vhxk2a9",
"type": "payment.succeeded",
"created": 1755388800,
"data": {
"id": "pay_...",
"amount": 49.00,
"method": "CARD",
"surcharge": 0,
"invoice_id": "inv_...",
"invoice_number": 1042,
"contact_id": "ct_...",
"contact_email": "[email protected]",
"subscription_id": "sub_...", // null for one-time payments
"checkout_link_id": "cl_...", // null off-link (invoice payments)
"reference": "cus_8fj2k1", // your ?ref=, null if none
"processor_ref": "TR...",
"pending": false,
"paid_at": "2026-08-17T14:00:00.000Z"
}
}subscription.created — fired once, when a subscription link's first payment succeeds:
{
"type": "subscription.created",
"data": {
"id": "sub_...",
"name": "Pro plan",
"interval": "MONTHLY",
"unit_price": 49.00,
"quantity": 1,
"status": "ACTIVE",
"contact_id": "ct_...",
"contact_email": "[email protected]",
"checkout_link_id": "cl_...",
"checkout_link_slug": "pro-plan",
"reference": "cus_8fj2k1",
"first_invoice_id": "inv_...",
"first_payment_id": "pay_..."
}
}REST API
For creating things programmatically, /api/v1 exposes customers, invoices, payments, and checkout links. Authenticate with an API key from Settings → Developers (Authorization: Bearer lv_live_… — shown once at creation), 300 requests per key per minute:
curl https://paywithlivery.com/api/v1/links \
-H "Authorization: Bearer lv_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Pro plan",
"amount": 49.00,
"slug": "pro-plan",
"success_url": "https://yoursite.com/thanks"
}'Questions, or need an event we don't send yet? Get in touch.
