Skip to content
Last updated

Use-case playbooks

All payloads on this page assume you've completed the Quick Start, have a valid Access Token, and send AccessToken, InterfaceVersion, InterfaceName, and CompanyName headers on every request. Amounts use major units of the chosen currency (10 = $10.00). restrictions.dates.activatesAt / expiresAt are ISO 8601 strings.


Invoice / A/R collections

When to use

You generate invoices in your billing system and want customers to pay online without handling card data. Each link maps to exactly one invoice.

Field ValueWhy
paymentLink.lineItems[]One product — name, currencyCode, and amount.total; see Create linkDefines what the customer is paying for; amount must match the invoice total exactly
paymentLink.restrictions.payments.limit1Prevents double-payment on the same invoice
paymentLink.restrictions.dates.expiresAtInvoice due date (ISO 8601)Link auto-expires when payment is overdue; triggers payment_link_updated
paymentLink.vendorReferenceYour internal invoice ID (max 50 chars)Reportable in the Merchant Portal and settlement extract — primary reconciliation key
paymentLink.staticFieldsInvoice number, Account IDKey/value map of read-only context shown to the customer on the checkout page
paymentLink.customFieldsPO number (optional), VAT number (optional)Use only if the customer needs to supply data you don't have
paymentLink.collectBillingAddresstrue (default)Required for VAT / tax receipts in most jurisdictions
paymentLink.notifications.share.emailtrueDelivers the link at creation time; no separate send step needed
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Consulting services — March 2026",
          "currencyCode": "USD",
          "amount": {
            "total": 1200
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 1 },
      "dates": { "expiresAt": "2026-04-01T00:00:00.000Z" }
    },
    "vendorReference": "INV-2026-00441",
    "staticFields": {
      "Invoice number": "INV-2026-00441",
      "Account ID": "ACC-8821"
    },
    "customFields": [
      { "key": "po_number", "label": "PO number", "optional": true }
    ],
    "collectBillingAddress": true,
    "notifications": {
      "share": { "email": true }
    }
  },
  "customer": { "id": "cust_oM1i4j7EsIY7W5vqqF76diGw" },
  "metadata": { "billing_run": "march-2026" }
}

Sharing pattern

Pass paymentLink.notifications.share.email: true at creation — the link is sent immediately to the customer's email. For bulk invoicing, create links programmatically, one per invoice.

If you embed the link in your own invoice template, omit notifications.share and use the paymentLink.url field from the response instead.

Reconciliation

  • Listen for checkout_session_completed — the payload includes paymentLink.id and the full checkoutSession (with staticFields, customFields, and the underlying lastTransaction).
  • Retrieve the payment link by ID to read vendorReference and reconcile against your billing record (alternatively, look the invoice number up directly in checkoutSession.staticFields["Invoice number"]).
  • Listen for payment_link_updated and, if paymentLink.status is expired, trigger a past-due reminder workflow.

Gotchas

  • A completed link (limit reached) cannot be reactivated. If a charge is disputed and reversed, create a new link.
  • The link expires at the exact moment of expiresAt. If a customer is mid-session when the link expires, their payment attempt will fail — communicate the deadline clearly in the email.
  • vendorReference is capped at 50 characters — use a short identifier, not a full description.

Deposits / reservations

When to use

You need to secure a booking (hotel room, car rental, appointment slot) by collecting payment upfront, with a hard deadline after which the reservation is released.

Field ValueWhy
paymentLink.lineItems[]Product name, currencyCode, and amount.total — required; see Create linkDescribes what the deposit covers (e.g. room type, dates)
paymentLink.restrictions.payments.limit1One deposit per reservation
paymentLink.restrictions.dates.expiresAtBooking deadline (e.g. 48 h from now, ISO 8601)Auto-releases the slot if the customer doesn't pay in time
customer.idExisting customer recordEnables email delivery (the contact info comes from the customer record)
paymentLink.notifications.share.emailtrueSends confirmation request immediately
paymentLink.collectBillingAddresstrueNeeded for refund processing if reservation is cancelled
paymentLink.vendorReferenceReservation / booking IDPrimary key for matching the deposit to your booking system
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Room deposit — Deluxe Suite 14–16 June",
          "currencyCode": "EUR",
          "amount": {
            "total": 250
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 1 },
      "dates": { "expiresAt": "2026-06-05T00:00:00.000Z" }
    },
    "vendorReference": "RES-77320",
    "collectBillingAddress": true,
    "notifications": {
      "share": { "email": true }
    }
  },
  "customer": { "id": "cust_oM1i4j7EsIY7W5vqqF76diGw" }
}

Sharing pattern

Create the link when the reservation is initiated (for example, after a phone call or form submission). Use paymentLink.notifications.share.email: true to reach the customer as soon as the link is ready.

Reconciliation

  • checkout_session_completed → confirm the reservation in your booking system.
  • payment_link_updated with paymentLink.status: "expired" → trigger cancellation and release the slot.
  • Match via paymentLink.vendorReference (reservation ID), read from the payment link object you retrieve by ID.

Gotchas

  • Expired links cannot be reactivated. If the customer asks for more time, create a new link with an extended expiresAt and deactivate the original.
  • If activatesAt and expiresAt are both set, the API requires expiresAt > activatesAt — ensure your date logic accounts for this.

Ticketing / admissions

When to use

You are selling a fixed number of seats, tickets, or admission slots for a specific event. Capacity is the binding constraint.

Field ValueWhy
paymentLink.lineItems[]Ticket name, currencyCode, and amount.total — required; see Create linkDescribes the event and admission type being sold
paymentLink.restrictions.payments.limitNumber of available seatsLink auto-completes when capacity is reached
paymentLink.restrictions.dates.expiresAtEvent start time (ISO 8601)Stops sales automatically when the event begins
paymentLink.customFieldsAttendee name (required), dietary/accessibility requirements (optional)Collects per-attendee data at checkout
paymentLink.staticFieldsEvent ID, Venue, Event datePass-through data for your ticketing system — appears on every checkout session
paymentLink.collectBillingAddressfalseNot needed for most ticketing scenarios
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "TechConf 2026 — General Admission",
          "description": "Full-day access on July 1, Main Hall, Chicago",
          "currencyCode": "USD",
          "amount": {
            "total": 89
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 250 },
      "dates": { "expiresAt": "2026-07-01T09:00:00.000Z" }
    },
    "staticFields": {
      "Event ID": "EVT-2026-TC",
      "Venue": "Main Hall, Chicago",
      "Event date": "2026-07-01"
    },
    "customFields": [
      { "key": "attendee_name", "label": "Attendee name",        "optional": false },
      { "key": "dietary",       "label": "Dietary requirements", "optional": true  }
    ],
    "collectBillingAddress": false
  }
}

Sharing pattern

Publish the single link URL on your event page, social channels, and email campaigns. No customer pre-binding — anyone with the URL can purchase.

Reconciliation

  • Each checkout_session_completed event = one ticket sold.
  • The session payload already carries customFields (attendee answers) and staticFields — no extra round-trip needed for the common case.
  • Track remaining capacity by retrieving the payment link and reading restrictions.payments.count (running total) against restrictions.payments.limit.
  • payment_link_updated with paymentLink.status: "completed" → trigger "sold out" messaging.

Gotchas

  • A completed link cannot be reactivated. If you release additional capacity, create a new link for the extra seats — do not rely on reactivation.
  • Abandoned checkouts do not consume a slot. Only successful payments increment restrictions.payments.count.
  • Race condition at capacity boundary: Two customers can open checkout simultaneously as the last seat sells. The second payment will be rejected after the first one increments count to the limit. Display a "might sell out soon" notice rather than a hard capacity guarantee.
  • customFields[].label is capped at 20 characters — keep labels concise.

Trips / school group payments

When to use

You need to collect the same fixed amount from multiple known participants: a school trip, a team event, a group booking. You have a list of names and want to track who has paid.

Approach HowBest when
One shared linkSingle link, restrictions.payments.limit = group size, custom field for participant nameSmaller groups, informal settings, you trust participants not to pay twice
One link per participantLink per customer, restrictions.payments.limit: 1, email delivery per personSchools, formal collections, you need a clear per-person audit trail
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Year 10 Paris Trip — payment",
          "currencyCode": "GBP",
          "amount": {
            "total": 345
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 32 },
      "dates": { "expiresAt": "2026-05-20T00:00:00.000Z" }
    },
    "staticFields": {
      "Trip ID": "TRIP-2026-PARIS"
    },
    "customFields": [
      { "key": "student_name", "label": "Student name", "optional": false },
      { "key": "parent_email", "label": "Parent email", "optional": false }
    ]
  }
}
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Year 10 Paris Trip — Emma Johnson",
          "currencyCode": "GBP",
          "amount": {
            "total": 345
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 1 },
      "dates": { "expiresAt": "2026-05-20T00:00:00.000Z" }
    },
    "vendorReference": "TRIP-2026-PARIS-EJ",
    "notifications": {
      "share": { "email": true }
    }
  },
  "customer": { "id": "cust_oM1i4j7EsIY7W5vqqF76diGw" }
}

Sharing pattern

  • Shared link: Paste the paymentLink.url into a group message, school app, or newsletter.
  • Per-participant: Create links in bulk via API and send each via paymentLink.notifications.share.email: true.

Reconciliation

For the shared-link approach, list the checkout sessions filtered by the link's ID (GET /checkoutsessions/list with the PaymentLinkId header). Each session has the participant name in its customFields. Cross-reference with your attendance list.

For per-participant links, match paymentLink.vendorReference (participant ID) to your records. Listen for payment_link_updated with paymentLink.status: "expired" to identify who has not yet paid.

Gotchas

  • Shared link: A participant who opens the link twice could pay twice if two checkout sessions complete before the limit is checked. Use per-participant links where strict one-payment guarantees matter.
  • Per-participant links: Notifications are sent once at link creation; there is no resend endpoint. If a customer loses their email, share paymentLink.url over another channel or create a new link.
  • Set restrictions.dates.expiresAt a few days before the actual trip date to give organisers time to chase outstanding payments.

When to use

A support agent needs to collect payment from a customer during or after a call — without reading card numbers over the phone or handling card data.

Field ValueWhy
paymentLink.lineItems[]Product or service name, currencyCode, and amount.total — required; see Create linkDescribes what the customer is paying for; agent fills this in based on the case
paymentLink.restrictions.payments.limit1One payment resolves one support case
paymentLink.restrictions.dates.expiresAt24–72 h from now (ISO 8601)Short window reduces risk of a forgotten active link; follow up if not paid
customer.idExisting customer record (looked up by agent)Associates payment to a known customer; enables email delivery
paymentLink.notifications.share.emailtrue or false (agent's choice)Delivers link immediately; agent can also paste it into chat
paymentLink.vendorReferenceSupport ticket / case numberTies the payment to the CRM record without any manual matching
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Replacement part — Order #ORD-9981",
          "currencyCode": "USD",
          "amount": {
            "total": 49
          }
        }
      }
    ],
    "restrictions": {
      "payments": { "limit": 1 },
      "dates": { "expiresAt": "2026-05-21T00:00:00.000Z" }
    },
    "vendorReference": "CASE-20260315-4421",
    "notifications": {
      "share": { "email": true }
    }
  },
  "customer": { "id": "cust_oM1i4j7EsIY7W5vqqF76diGw" }
}

Sharing pattern

The agent creates the link from the back office or support tool (via API). paymentLink.notifications.share.email: true dispatches it immediately. For live chat support, the agent can also paste paymentLink.url directly into the conversation.

Reconciliation

  • checkout_session_completed → auto-close or update the support ticket in your CRM by matching paymentLink.vendorReference (retrieve the link by ID from the webhook payload's paymentLink.id).
  • payment_link_updated with paymentLink.status: "expired" → flag the case for follow-up.

Gotchas

  • Short expiry is important — an active link with no expiry that a customer has forgotten about is a security risk. 24–72 h is a sensible default.
  • Verify contact details before creating the link. The notification destination is captured at creation time; there is no resend endpoint, so a wrong address requires a new link.
  • If the customer claims they didn't receive the email, share paymentLink.url over another channel or create a new link — deactivate the original first to avoid two active payment paths for the same case.

Verify a card without charging

When to use

You want to confirm that a customer has a real, valid card — and that there's a genuine person behind it — without taking any money. A card-verification link runs a $0 authorization against the card: it succeeds only if the card is live and passes the issuer's checks. Use it as a security / legitimacy check before granting access, starting a free trial, onboarding a new account, or shipping goods you'll bill for separately.

As a side effect, the verified card is saved to the customer, so when that same customer later opens another payment link from your account, the card appears as a pick-list option on the hosted checkout — one less thing for a returning customer to type. (If all you need is to let customers reuse a saved card on a regular payment, you don't need a verification link at all — set collectBillingAddress/allowSavedCards on a normal line_items link. Reach for card_verification when the validation itself is the point and you don't want to charge.)

A card-verification link is shaped differently from a payment link: it has no lineItems, but it requires paymentLink.type: "card_verification", a top-level paymentLink.currency, and a customer.

Field ValueWhy
paymentLink.typecard_verificationSelects the validate-and-save flow instead of a payment
paymentLink.currencyISO 4217 code, e.g. USDRequired for card verification — there are no line items to infer it from
customerExisting id, or an inline customer (at minimum emailAddress)The verification is tied to this customer; the validated card is also saved to them for later links
paymentLink.restrictions.payments.limit1One verification per link; the link completes once the card is saved
paymentLink.notifications.share.emailtrue or falseDelivers the link to the customer; you can also share paymentLink.url directly
{
  "paymentLink": {
    "type": "card_verification",
    "currency": "USD",
    "restrictions": {
      "payments": { "limit": 1 }
    },
    "notifications": {
      "share": { "email": true }
    }
  },
  "customer": { "id": "cust_oM1i4j7EsIY7W5vqqF76diGw" }
}

Sharing pattern

Send the link the same way as any other — notifications.share.email: true dispatches it, or paste paymentLink.url into your own onboarding flow. The customer opens the link, enters their card once, and the hosted checkout confirms the $0 authorization.

Reconciliation

  • checkout_session_completed → the card passed verification (and was saved to the customer). There is no charge to reconcile; treat the event as "card confirmed" and proceed with whatever the check was gating — granting access, starting the trial, releasing the order.
  • Match the customer via paymentLink.id (retrieve the link) or the customer object on the webhook payload.

Gotchas

  • No charge happens. Card verification is a $0 authorization — it confirms the card is valid and saves it, but it is not a held-funds pre-authorization and does not set up any automatic or recurring billing. To collect money, send a normal payment link.
  • Verification can still fail. A declined or invalid card means no checkout_session_completed — don't grant access or start the trial until you see the event.
  • Saving a card doesn't require verification. If your goal is just to let returning customers reuse a card on a regular payment, use a normal line_items link rather than a verification link. Reach for card_verification when validating the card itself is the point.
  • lineItems and card_verification don't mix. A verification link has no line items; supplying both is rejected.
  • A customer is mandatory. The verification has to be tied to someone — without a customer (inline or by id) the request is rejected.

Pay-what-you-want

When to use

You want customers to pay freely chosen amounts (or choose from preset options) rather than a fixed price — pay-what-you-want checkouts, tips, donations, or fundraising campaigns. Volume is unlimited and links are shared publicly.

Field ValueWhy
paymentLink.lineItems[]Exactly one product with a currencyCode and a customer-chosen amount — preset options, a custom range, or both; see Create linkVariable amount lets the customer choose how much to pay; preset options anchor toward higher amounts, custom range enforces floor and ceiling
paymentLink.restrictions.dates.expiresAtCampaign end date (ISO 8601)Auto-closes fundraising on schedule
paymentLink.customFieldsDonor message (optional), in memory of (optional)Personal touch; appears in the checkout session for your records
paymentLink.collectBillingAddresstrue (default)Required for gift aid eligibility in some jurisdictions
paymentLink.localeTarget region locale (e.g. en, de, fr)Checkout UI renders in the donor's language
{
  "paymentLink": {
    "lineItems": [
      {
        "product": {
          "name": "Support our food bank",
          "currencyCode": "GBP",
          "amount": {
            "options": [10, 25, 50, 100]
          }
        }
      }
    ],
    "restrictions": {
      "dates": { "expiresAt": "2026-09-01T00:00:00.000Z" }
    },
    "customFields": [
      { "key": "donor_message", "label": "Leave a message", "optional": true },
      { "key": "in_memory_of",  "label": "In memory of",    "optional": true }
    ],
    "collectBillingAddress": true,
    "locale": "en"
  }
}

Use amount.custom instead of amount.options if you want an open-ended range:

{
  "product": {
    "name": "Pay what you want",
    "currencyCode": "GBP",
    "amount": {
      "custom": { "min": 1, "max": 10000 }
    }
  }
}

Sharing pattern

Embed paymentLink.url in your campaign website, email newsletter, and social media posts. No customer binding is required — the link is publicly accessible. For QR-code campaigns (physical materials, events), generate a QR code from the URL.

Reconciliation

  • Each checkout_session_completed = one payment. The session's lastTransaction contains the amount the customer chose; customFields contains any answers.
  • For campaign totals, list checkout sessions filtered by PaymentLinkId and sum lastTransaction amounts.
  • payment_link_updated with paymentLink.status: "expired" → trigger campaign-close communications.

Gotchas

  • Customer-chosen amount links are API-only — they cannot be created from the Customer Hub.
  • A customer-chosen amount link contains exactly one line item. Multiple line items return an error.
  • amount.options, amount.custom, and amount.total are mutually exclusive — exactly one must be present per product.
  • custom.min and custom.max should both be provided when using the custom range — setting only one leaves the range undefined.
  • Omitting restrictions.payments.limit lets the link receive unlimited payments — the intended behaviour for fundraising. Only set a limit if your campaign has a fixed number of "slots" (e.g. sponsorship tiers).