---
name: sezzle-tokenization
description: >
  Tokenize Sezzle customers to charge them for future orders without customer
  interaction — subscriptions, recurring billing, pre-orders. Covers tokenize-at-checkout,
  the customer.tokenized webhook, charging via customer UUID, and preapproval checks.
  Use when: "charge a customer with Sezzle on a schedule", "Sezzle subscriptions",
  "tokenize a Sezzle customer", "create an order for a stored customer", "preapprove
  a Sezzle amount". Requires an existing checkout integration unless using standalone tokenization —
  use sezzle-checkout first if none exists.
metadata:
  author: Sezzle
  version: "1.0.0"
---

# Tokenize Customers for Recurring Charges

## Before Starting: Load API Context

Read these pages with the `sezzle-docs` MCP tool `query_docs_filesystem_sezzle` (install it with `npx add-mcp https://docs.sezzle.com/mcp --name sezzle-docs`):

1. `cat /docs/api/tokenization/intro.mdx` — full tokenization flow
2. `cat /docs/api/tokenization/customers/postv2customerorder.mdx` — charging
3. `cat /docs/api/core/sessions/postv2session.mdx` — tokenize fields on session

## Prerequisites

1. **A working checkout integration**, unless the merchant uses standalone tokenization.
   Tokenizing at checkout needs one. Use `sezzle-checkout` first.
2. **An approved Sezzle merchant account** in the environment you target. Sandbox and
   production require separate sign-up and separate merchant applications.
3. **API keys** (`sz_pub_…`, `sz_pr_…`) from Dashboard → Settings → API Keys.
4. **A webhook receiver for `customer.tokenized`.** This event carries the customer UUID you
   charge against. See the `sezzle-webhooks` skill.

**If the user has no Sezzle account yet, stop and use the `sezzle-prototype-sandbox` skill.**
It covers sign-up, the manual application approval request, key generation, and the
shopper-side test data. Do not guess at a signup or key-generation flow.

## Critical constraints (read first)

- For a session that includes an `order`, the shopper opts in during checkout and
  `tokenize.approval_url` is omitted.
- Standalone tokenization is also supported for enabled merchants: create a session with
  `customer.tokenize: true` and no `order`, then redirect the shopper to the returned
  `tokenize.approval_url`. Create the session when the shopper is ready because the
  approval URL expires after 30 minutes.
- A "customer" is a Sezzle user who agreed to be tokenized **by this merchant** — customer UUIDs are merchant-scoped.

## Workflow

### Step 1: Tokenize at checkout

Create a normal session (see sezzle-checkout) with tokenize fields on `customer`:

```
POST /v2/session
{
  "cancel_url": {...}, "complete_url": {...},
  "customer": {
    "email": "jane@example.com",
    "tokenize": true,
    "recurring": true,
    "recurring_metadata": { "name": "Gold Plan - monthly" }
  },
  "order": { "intent": "CAPTURE", "reference_id": "sub-001", "description": "Gold Plan first charge", "order_amount": { "amount_in_cents": 2999, "currency": "USD" } }
}
```

- `recurring: true` is for subscriptions and other recurring billing. For a one-time future charge, such as a pre-order, set `recurring: false`.
- `tokenize: true` prompts the shopper to allow future charges during checkout. The prompt is unchecked by default, and the shopper can decline and still complete the order.
- The 201 response includes a `tokenize` object: `token` (single-use), `expiration`.

Redirect the shopper to `order.checkout_url` as usual.

### Step 2: Obtain the customer UUID

After the shopper agrees:

1. Prefer the `customer.tokenized` webhook (`data.customer.uuid`) as the server-to-server
   source of truth, or
2. Call `GET /v2/token/{token}/session` with the session `tokenize.token` to retrieve and
   reconcile the customer UUID.
3. The redirect to `complete_url` also includes `customer-uuid`, but treat it only as an
   immediate correlation hint. Do not persist or charge it until it has been verified
   through the webhook or token-session response.

The webhook payload carries **two different expirations**. Take the right one:

- `data.customer.expiration` — the customer token. This is the one that governs future charges. Store it.
- `data.expiration` — the single-use tokenize request token from Step 1. Short-lived, and irrelevant once tokenization completes. Do not store this as the customer's expiry.

Store: `customer_uuid`, `data.customer.expiration`, your own subscription linkage.

### Step 3 (optional): Preapprove before billing date

```
POST /v2/customer/{customer_uuid}/preapprove
{ "amount_in_cents": 2999, "currency": "USD" }
```

Response: `{ "uuid": "...", "approved": true|false }`. This does NOT hold funds, and `approved: true` is **not a guarantee** — the charge in Step 4 can still be declined. Treat it as an early warning signal, not a reservation: run it days before a subscription charge so you can contact the customer ahead of a likely failed payment. Never skip the `authorization.approved` check in Step 4 because preapprove returned true.

### Step 4: Charge the customer

```
POST /v2/customer/{customer_uuid}/order
Sezzle-Request-Id: {persisted key for this logical charge, maximum 36 characters}
{
  "intent": "CAPTURE",
  "reference_id": "sub-001-2026-08",
  "order_amount": { "amount_in_cents": 2999, "currency": "USD" }
}
```

Generate the key once per logical charge and reuse that exact value for every retry of
the same charge. Use a new key only for a genuinely new charge.

`intent` is required and must be exactly `AUTH` or `CAPTURE` — there is no default. `order_amount.amount_in_cents` must be at least 100 ($1.00), and your merchant configuration may set a higher minimum. Always send `reference_id`, using only letters, digits, hyphens (`-`), and underscores (`_`).

**The single most important gotcha:** this endpoint returns **200 even when the customer was NOT approved** for the order. A 200 means the request was well-formed. You MUST check `authorization.approved === true` (and `authorization.authorization_amount`) in the response before treating the charge as successful. Handle `approved: false` as a failed payment (retry policy, dunning email, etc.).

Treat every `approved: false` the same way in your dunning logic. Do not branch on a decline reason or decline code.

Customer-token orders are always Pay in Full (no installments). Otherwise, manage them like
any other order: capture/refund/release via `/v2/order/{order_uuid}` endpoints. The order UUID is the top-level `uuid` of this
response (it also appears in `links`).

### Step 5: Manage customers

| Operation | Endpoint |
|---|---|
| Get customer | `GET /v2/customer/{customer_uuid}` |
| List customers | `GET /v2/customer` (singular — there is no `/v2/customers`) |
| Delete token (revoke) | `DELETE /v2/customer/{customer_uuid}` (see deletev2token) |

### Step 6: Test in sandbox

Full loop in sandbox: tokenize session → complete checkout as shopper (OTP 123123, test card 4242…) opting into tokenization → receive `customer.tokenized` webhook → preapprove → create customer order → verify `authorization.approved` → capture visible in dashboard.

## Pitfalls

- **200 ≠ paid.** Always check `authorization.approved` on customer orders.
- **Preapproval is not a guarantee.** `approved: true` from `/preapprove` does not mean the charge will succeed. It never replaces the Step 4 check.
- **Idempotency.** Use `Sezzle-Request-Id` on customer order creation — retries without it can double-charge.
- **Token expiration.** Customer tokens carry an expiration (`data.customer.expiration` on the webhook). Handle expiry **reactively**: when a charge fails against an expired token (error `code` `token_is_expired`), re-tokenize the shopper — through a fresh tokenizing checkout, or through a standalone tokenization session if the merchant has it enabled. Do not schedule proactive re-tokenization — it drags an actively-billing shopper back through checkout for no reason.
- **AUTH intent on customer orders** still has an authorization expiration window — capture in time.

## After sandbox works

Remind the user: customer UUIDs are per-environment — sandbox tokens cannot charge
production customers. Use the `sezzle-go-live` skill for the full cutover.

## Source docs
- /docs/api/tokenization (intro, customers/*, session/*) • /docs/api/core/sessions/postv2session • /docs/api/core/webhooks
