# Use-case playbooks

- [Invoice / A/R collections](#invoice--ar-collections)
- [Deposits / reservations](#deposits--reservations)
- [Ticketing / admissions](#ticketing--admissions)
- [Trips / school group payments](#trips--school-group-payments)
- [Customer support / call center pay links](#customer-support--call-center-pay-links)
- [Verify a card without charging](#verify-a-card-without-charging)
- [Pay-what-you-want](#pay-what-you-want)


All payloads on this page assume you've completed the [Quick Start](/guides/quickstart), 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.

### Recommended configuration

| Field  | Value | Why |
|  --- | --- | --- |
| `paymentLink.lineItems[]` | One product — `name`, `currencyCode`, and `amount.total`; see [Create link](/guides/core-concepts/payment-links/guides/create-link/api) | 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 |


```json
{
  "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.

### Recommended configuration

| Field  | Value | Why |
|  --- | --- | --- |
| `paymentLink.lineItems[]` | Product `name`, `currencyCode`, and `amount.total` — required; see [Create link](/guides/core-concepts/payment-links/guides/create-link/api#the-product-object) | 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 |


```json
{
  "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.

### Recommended configuration

| Field  | Value | Why |
|  --- | --- | --- |
| `paymentLink.lineItems[]` | Ticket name, `currencyCode`, and `amount.total` — required; see [Create link](/guides/core-concepts/payment-links/guides/create-link/api) | 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 |


```json
{
  "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.

### When to choose one link vs. one link per participant

| 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 |


#### One shared link

```json
{
  "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 }
    ]
  }
}
```

#### One link per participant

```json
{
  "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.


## Customer support / call center pay links

### 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.

### Recommended configuration

| Field  | Value | Why |
|  --- | --- | --- |
| `paymentLink.lineItems[]` | Product or service name, `currencyCode`, and `amount.total` — required; see [Create link](/guides/core-concepts/payment-links/guides/create-link/api) | 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 |


```json
{
  "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.)

### Recommended configuration

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 |


```json
{
  "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.

### Recommended configuration

| 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](/guides/core-concepts/payment-links/guides/create-link/api#create-a-customer-chosen-amount-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 |


```json
{
  "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:

```json
{
  "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).