Quadriel Quadriel ← Back to app

API guide

Everything you need to get from a fresh key to a first successful call, then to confirmed payments over webhooks. The API reference has the full endpoint list and schemas; this page is the narrative path through them.

Live keys, no separate sandbox. There is no sandbox host — you use the same base URL for both modes and switch by key: sk_test_… keys run in test mode, sk_live_… keys move real money. Create either from your dashboard.

1 · Get an API key

Secret keys authenticate your server-side calls. In the app, go to Developers → API keys (admins only) and Create a key. The full sk_test_… / sk_live_… secret is shown once at creation — store it somewhere safe; we only keep a prefix. Rotate or revoke a key from the same screen at any time.

Developers → API keys
API keys
Secret keys authenticate your server-side calls to the Quadriel API.
Create a key
Name it after where you'll use it (e.g. "Production server").
Production server
+ Create key
Your keys
We only store a prefix — the full secret is shown once at creation.
Production server ACTIVE
sk_live_9x2Q… · Created 12 Aug 2026 · Last used today
RotateRevoke
Old laptop REVOKED
sk_test_1a4F… · Created 3 Jul 2026 · Last used 30 Jul 2026
Remove
Developers → API keys — create a key, then rotate or revoke it any time.

Send it on every request as a bearer token:

Authorization: Bearer sk_test_your_key

Never expose a secret key in front-end code. (For browser checkout, use a publishable key with the Checkout Widget instead.)

2 · Quickstart — your first invoice

Create a customer, draft an invoice, then finalize it to issue a hosted payment link. Amounts are always in the currency's minor unit (pence for GBP — 10000 = £100.00). Pass an Idempotency-Key on writes so a retried request never double-creates.

# 1. Point at the API and your test key
export QUADRIEL_API="https://rifayrirolwkfilokjis.supabase.co/functions/v1/api/v1"
export QUADRIEL_KEY="sk_test_your_key"          # from Developers → API keys

# 2. Create a customer  →  { "customer_id": "...", ... }
curl -s "$QUADRIEL_API/customers" \
  -H "Authorization: Bearer $QUADRIEL_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "type": "company", "family_name": "Acme Ltd", "email": "ap@acme.example" }'

# 3. Create a draft invoice for that customer  →  { "id": "...", "total": 12000, "line_items": [...] }
curl -s "$QUADRIEL_API/invoices" \
  -H "Authorization: Bearer $QUADRIEL_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
        "customer_id": "CUSTOMER_ID_FROM_STEP_2",
        "collection_method": "send_invoice",
        "payment_method": "card",
        "currency": "GBP",
        "line_items": [
          { "description": "Consulting", "quantity": 1, "unit_amount": 10000, "vat_rate": 20 }
        ]
      }'

# 4. Finalize it  →  issues a number + a hosted payment link token
curl -s "$QUADRIEL_API/invoices/INVOICE_ID_FROM_STEP_3/finalize" \
  -X POST -H "Authorization: Bearer $QUADRIEL_KEY"
# => { "id": "...", "status": "open", "number": "INV-0001",
#      "payment_link_id": "...", "hosted_payment_url": "https://.../pay/<token>" }
# Send the payer to hosted_payment_url — it is ready to share as-is.

That's a complete round trip against the live API. Swap send_invoice for charge_automatically to collect without sending a link, and see the reference for payments, mandates and payment links.

3 · Errors, idempotency & rate limits

Errors come back as { "error": { "code": "…", "message": "…" } } with a matching HTTP status (400/401/403/404/409/422/429). Retry 429 and 5xx with backoff; on 2xx you're done. Reuse the same Idempotency-Key when you retry a write and you get the original result back, not a duplicate. The reference intro covers the exact money, pagination and rate-limit rules.

4 · Webhooks

Confirm outcomes server-side — don't rely on a browser redirect. Add your endpoint in Developers → Webhooks; we POST each event to it as JSON. Every request carries:

Developers → Webhooks
Webhooks
We POST a signed message to your server when things happen — so you can fulfil orders reliably.
Your endpoints
Where we send events. Each endpoint has its own signing secret, shown once when it's created or rotated.
https://yourshop.com/api/payments-webhook
+ Add
https://yourshop.com/api/payments-webhook ACTIVE
Signing secret whsec_•••••• · shown once · 6 events delivered
DeliveriesRotate
Developers → Webhooks — add an endpoint, copy its signing secret, then verify every delivery.

The body is the event payload plus a version field. Verify the signature over the raw bytes before trusting anything, and reply 2xx to acknowledge (we retry with backoff otherwise):

const crypto = require("crypto");

// Use the RAW request bytes — not a re-serialized object.
app.post("/quadriel/webhook", express.raw({ type: "application/json" }), (req, res) => {
  // Replay protection: reject stale timestamps, then verify the signature
  // over "<timestamp>.<raw body>". Retries carry a fresh timestamp+signature;
  // dedupe on event_reference in the payload.
  const timestamp = req.get("x-webhook-timestamp") || "";
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.sendStatus(401);
  }
  const expected = crypto
    .createHmac("sha256", process.env.QUADRIEL_WEBHOOK_SECRET)  // your endpoint's signing secret
    .update(timestamp + ".")                                    // signed timestamp prefix
    .update(req.body)                                           // then the raw Buffer
    .digest("hex");                                             // lowercase hex

  const got = req.get("x-webhook-signature") || "";
  if (got.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString());
  switch (req.get("x-webhook-event")) {
    case "invoice.paid":        /* mark the invoice paid in your system */ break;
    case "checkout.completed":  /* fulfil the order */                     break;
    case "collection.paid":     /* record the recurring collection */      break;
  }
  res.sendStatus(200);   // any 2xx acknowledges; non-2xx is retried with backoff
});

Events

EventWhen it fires
checkout.completedA hosted-checkout / widget flow finished — one-off paid, card saved, or DD mandate set up.
collection.paidA scheduled recurring collection was taken.
invoice.finalizedAn invoice was issued (number assigned, payment artefacts created).
invoice.paidAn invoice was paid in full.
invoice.payment_failedA collection against an invoice failed.
invoice.voidedAn invoice was voided before payment.
invoice.uncollectibleAn invoice was written off as uncollectible.
terminal.order_requestedA card-present terminal order was requested.

You can see real, recent payloads for your endpoint — and replay them — under Developers → Webhooks → Deliveries. Two common shapes:

checkout.completed

{
  "version": 1,
  "payment_id": "…",
  "customer_id": "…",
  "customer_external_id": "user-42",
  "method": "card",
  "structure": "one_time",
  "amount": 2500,
  "currency": "GBP",
  "reference": "ORDER-991"
}

collection.paid

{
  "version": 1,
  "collection_id": "…",
  "payment_id": "…",
  "customer_id": "…",
  "amount": 999,
  "currency": "GBP",
  "reference": "SUB-2026-001",
  "due_date": "2026-07-01"
}
Invoice events carry a hosted_token. Every invoice.* event includes a hosted_token — the bearer for that invoice's hosted payment page (/pay/<token>). Anyone holding it can view the payer's details (name, email, phone, address, tax IDs) and the invoice, and can start a card payment, until the link expires or is cancelled. It is the same link we email the payer, so it is not a new exposure — but it makes your endpoint URL, and any log that captures request bodies, as sensitive as the pay link itself. Treat it accordingly, and don't forward the token anywhere you wouldn't send the pay link.

5 · Changelog

DateChange
2026-09-20Documented that invoice.* webhook events include a hosted_token (the payer's hosted-payment-page link) and should be handled as sensitive.
2026-09-18Public REST API v1 documented: customers, invoices, payments, payment links, mandates, transactions, activity and webhook management. Signed webhooks (HMAC-SHA256). Embeddable Checkout Widget.

Building a browser checkout instead? See the Checkout Widget guide.