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

  1. 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.
  2. Link or redirect your customer to it, appending ?ref= with your own id for the customer or order.
  3. Receive a signed webhook when the payment succeeds (and when subscriptions start, renew, fail, or are canceled) with your ref echoed back — flip the switch in your system there.

Checkout link URL parameters

Every checkout link accepts three optional query parameters:

ParamWhat it does
refYour 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.
emailPrefills the payer's email (still editable).
namePrefills 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": { ... }
}
EventFires when
payment.succeededA payment lands — checkout link, invoice, or autopay.
payment.failedA charge is declined or errors at the processor.
payment.refundedA full or partial refund is issued.
invoice.paidAn invoice reaches fully paid.
subscription.createdA subscription checkout link's first payment succeeds and the recurring plan starts.
subscription.canceledA recurring plan is canceled.
dispute.createdA customer opens a chargeback.
test.pingYou 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.