- Invoice / A/R collections
- Deposits / reservations
- Ticketing / admissions
- Trips / school group payments
- Customer support / call center pay links
- Verify a card without charging
- Pay-what-you-want
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.
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 | Value | Why |
|---|---|---|
paymentLink.lineItems[] | One product — name, currencyCode, and amount.total; see Create link | Defines what the customer is paying for; amount must match the invoice total exactly |
paymentLink.restrictions.payments.limit | 1 | Prevents double-payment on the same invoice |
paymentLink.restrictions.dates.expiresAt | Invoice due date (ISO 8601) | Link auto-expires when payment is overdue; triggers payment_link_updated |
paymentLink.vendorReference | Your internal invoice ID (max 50 chars) | Reportable in the Merchant Portal and settlement extract — primary reconciliation key |
paymentLink.staticFields | Invoice number, Account ID | Key/value map of read-only context shown to the customer on the checkout page |
paymentLink.customFields | PO number (optional), VAT number (optional) | Use only if the customer needs to supply data you don't have |
paymentLink.collectBillingAddress | true (default) | Required for VAT / tax receipts in most jurisdictions |
paymentLink.notifications.share.email | true | Delivers 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" }
}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.
- Listen for
checkout_session_completed— the payload includespaymentLink.idand the fullcheckoutSession(withstaticFields,customFields, and the underlyinglastTransaction). - Retrieve the payment link by ID to read
vendorReferenceand reconcile against your billing record (alternatively, look the invoice number up directly incheckoutSession.staticFields["Invoice number"]). - Listen for
payment_link_updatedand, ifpaymentLink.statusisexpired, trigger a past-due reminder workflow.
- A
completedlink (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. vendorReferenceis capped at 50 characters — use a short identifier, not a full description.
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 | Value | Why |
|---|---|---|
paymentLink.lineItems[] | Product name, currencyCode, and amount.total — required; see Create link | Describes what the deposit covers (e.g. room type, dates) |
paymentLink.restrictions.payments.limit | 1 | One deposit per reservation |
paymentLink.restrictions.dates.expiresAt | Booking deadline (e.g. 48 h from now, ISO 8601) | Auto-releases the slot if the customer doesn't pay in time |
customer.id | Existing customer record | Enables email delivery (the contact info comes from the customer record) |
paymentLink.notifications.share.email | true | Sends confirmation request immediately |
paymentLink.collectBillingAddress | true | Needed for refund processing if reservation is cancelled |
paymentLink.vendorReference | Reservation / booking ID | Primary 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" }
}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.
checkout_session_completed→ confirm the reservation in your booking system.payment_link_updatedwithpaymentLink.status: "expired"→ trigger cancellation and release the slot.- Match via
paymentLink.vendorReference(reservation ID), read from the payment link object you retrieve by ID.
- Expired links cannot be reactivated. If the customer asks for more time, create a new link with an extended
expiresAtand deactivate the original. - If
activatesAtandexpiresAtare both set, the API requiresexpiresAt > activatesAt— ensure your date logic accounts for this.
You are selling a fixed number of seats, tickets, or admission slots for a specific event. Capacity is the binding constraint.
| Field | Value | Why |
|---|---|---|
paymentLink.lineItems[] | Ticket name, currencyCode, and amount.total — required; see Create link | Describes the event and admission type being sold |
paymentLink.restrictions.payments.limit | Number of available seats | Link auto-completes when capacity is reached |
paymentLink.restrictions.dates.expiresAt | Event start time (ISO 8601) | Stops sales automatically when the event begins |
paymentLink.customFields | Attendee name (required), dietary/accessibility requirements (optional) | Collects per-attendee data at checkout |
paymentLink.staticFields | Event ID, Venue, Event date | Pass-through data for your ticketing system — appears on every checkout session |
paymentLink.collectBillingAddress | false | Not 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
}
}Publish the single link URL on your event page, social channels, and email campaigns. No customer pre-binding — anyone with the URL can purchase.
- Each
checkout_session_completedevent = one ticket sold. - The session payload already carries
customFields(attendee answers) andstaticFields— no extra round-trip needed for the common case. - Track remaining capacity by retrieving the payment link and reading
restrictions.payments.count(running total) againstrestrictions.payments.limit. payment_link_updatedwithpaymentLink.status: "completed"→ trigger "sold out" messaging.
- 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
countto thelimit. Display a "might sell out soon" notice rather than a hard capacity guarantee. customFields[].labelis capped at 20 characters — keep labels concise.
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 | How | Best when |
|---|---|---|
| One shared link | Single link, restrictions.payments.limit = group size, custom field for participant name | Smaller groups, informal settings, you trust participants not to pay twice |
| One link per participant | Link per customer, restrictions.payments.limit: 1, email delivery per person | Schools, 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" }
}- Shared link: Paste the
paymentLink.urlinto a group message, school app, or newsletter. - Per-participant: Create links in bulk via API and send each via
paymentLink.notifications.share.email: true.
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.
- 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.urlover another channel or create a new link. - Set
restrictions.dates.expiresAta few days before the actual trip date to give organisers time to chase outstanding payments.
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 | Value | Why |
|---|---|---|
paymentLink.lineItems[] | Product or service name, currencyCode, and amount.total — required; see Create link | Describes what the customer is paying for; agent fills this in based on the case |
paymentLink.restrictions.payments.limit | 1 | One payment resolves one support case |
paymentLink.restrictions.dates.expiresAt | 24–72 h from now (ISO 8601) | Short window reduces risk of a forgotten active link; follow up if not paid |
customer.id | Existing customer record (looked up by agent) | Associates payment to a known customer; enables email delivery |
paymentLink.notifications.share.email | true or false (agent's choice) | Delivers link immediately; agent can also paste it into chat |
paymentLink.vendorReference | Support ticket / case number | Ties 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" }
}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.
checkout_session_completed→ auto-close or update the support ticket in your CRM by matchingpaymentLink.vendorReference(retrieve the link by ID from the webhook payload'spaymentLink.id).payment_link_updatedwithpaymentLink.status: "expired"→ flag the case for follow-up.
- 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.urlover another channel or create a new link — deactivate the original first to avoid two active payment paths for the same case.
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 | Value | Why |
|---|---|---|
paymentLink.type | card_verification | Selects the validate-and-save flow instead of a payment |
paymentLink.currency | ISO 4217 code, e.g. USD | Required for card verification — there are no line items to infer it from |
customer | Existing 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.limit | 1 | One verification per link; the link completes once the card is saved |
paymentLink.notifications.share.email | true or false | Delivers 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" }
}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.
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 thecustomerobject on the webhook payload.
- 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_itemslink rather than a verification link. Reach forcard_verificationwhen validating the card itself is the point. lineItemsandcard_verificationdon't mix. A verification link has no line items; supplying both is rejected.- A
customeris mandatory. The verification has to be tied to someone — without acustomer(inline or byid) the request is rejected.
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 | Value | Why |
|---|---|---|
paymentLink.lineItems[] | Exactly one product with a currencyCode and a customer-chosen amount — preset options, a custom range, or both; see Create link | Variable amount lets the customer choose how much to pay; preset options anchor toward higher amounts, custom range enforces floor and ceiling |
paymentLink.restrictions.dates.expiresAt | Campaign end date (ISO 8601) | Auto-closes fundraising on schedule |
paymentLink.customFields | Donor message (optional), in memory of (optional) | Personal touch; appears in the checkout session for your records |
paymentLink.collectBillingAddress | true (default) | Required for gift aid eligibility in some jurisdictions |
paymentLink.locale | Target 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 }
}
}
}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.
- Each
checkout_session_completed= one payment. The session'slastTransactioncontains the amount the customer chose;customFieldscontains any answers. - For campaign totals, list checkout sessions filtered by
PaymentLinkIdand sumlastTransactionamounts. payment_link_updatedwithpaymentLink.status: "expired"→ trigger campaign-close communications.
- 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, andamount.totalare mutually exclusive — exactly one must be present per product.custom.minandcustom.maxshould both be provided when using the custom range — setting only one leaves the range undefined.- Omitting
restrictions.payments.limitlets 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).