---
name: sezzle-prototype-sandbox
description: >
  Working Sezzle sandbox demo with no production setup: create checkouts,
  complete them with test data, exercise release/capture/refund. Start here
  if you have no Sezzle credentials; covers sandbox sign-up, merchant app
  approval, and API key generation. Use when: "prototype with Sezzle",
  "test the Sezzle API", "try a Sezzle checkout in sandbox", "demo Sezzle
  payments", "get Sezzle test data", "get Sezzle API keys", "initial setup
  for Sezzle", "I have no Sezzle credentials yet". Throwaway calls only, no
  integration code. Sandbox only, so no real money moves.
compatibility: Requires curl or any HTTP client, plus a browser for the sandbox dashboard and shopper checkout. No Sezzle production account needed.
metadata:
  author: Sezzle
  version: "1.0.0"
---

# Prototype with the Sezzle Sandbox

Everything here runs against `https://sandbox.gateway.sezzle.com/v2` with sandbox credentials. **Never use production keys or the production base URL with this skill.**

## Workflow

### Step 1: Sandbox account + API keys

If the project has a `.env`, check for saved keys first, testing for presence without printing the values:

```bash
grep -q '^SEZZLE_SANDBOX_PUBLIC_KEY=.' .env && grep -q '^SEZZLE_SANDBOX_PRIVATE_KEY=.' .env
```

If both are set, skip to Step 2. If there's no project to hold a `.env`, ask the user whether they already have sandbox keys before sending them through sign-up.

**Otherwise this step is the user's to perform — sign-up and the dashboard are browser-only. Hand off the instructions below, then resume at Step 2 once they confirm keys are saved.** Sandbox is standalone; no production account is needed:

1. Sign up: https://sandbox.dashboard.sezzle.com/merchant/signup (any valid phone number; no OTPs are sent in sandbox)
2. Submit the merchant application — the info doesn't need to be real
3. Application approval is manual: contact your account manager or Sezzle Support (sandbox apps are reviewed on request)
4. Once approved: Settings → API Keys (Admin only) → generate keys
5. Save to `.env` as `SEZZLE_SANDBOX_PUBLIC_KEY` / `SEZZLE_SANDBOX_PRIVATE_KEY`; add `.env` to `.gitignore`

Note: unlike production, sandbox does NOT require linking a card or bank account before generating API keys.

### Step 2: Authenticate

```
POST https://sandbox.gateway.sezzle.com/v2/authentication
{ "public_key": "...", "private_key": "..." }
```

Read the keys from the environment at call time instead of inlining their values:

```bash
curl -s https://sandbox.gateway.sezzle.com/v2/authentication \
  -H 'Content-Type: application/json' \
  -d "{\"public_key\":\"$SEZZLE_SANDBOX_PUBLIC_KEY\",\"private_key\":\"$SEZZLE_SANDBOX_PRIVATE_KEY\"}"
```

Use `Authorization: Bearer {token}` on all calls. Token lasts 120 minutes.

### Step 3: Create a test checkout

```
POST /v2/session
{
  "cancel_url":   { "href": "https://example.com/cancel" },
  "complete_url": { "href": "https://example.com/complete" },
  "order": {
    "intent": "AUTH",
    "reference_id": "proto-1",
    "description": "Prototype order",
    "order_amount": { "amount_in_cents": 2500, "currency": "USD" }
  }
}
```

The response carries both values you need:
- `order.checkout_url` — open in a browser to complete checkout
- `order.uuid` — **keep this**; it is the `{order_uuid}` for every call in Step 5

Tips that keep testing friction low:
- Use `intent: AUTH` and keep amounts small. An authorization still holds the test shopper's spending power — what auth-only buys you is the ability to reclaim it by releasing the order or letting the authorization expire
- Optionally set the authorization expiration to 30 minutes in Dashboard → Settings → **Payment Captures** (that menu item lives at `/merchant/settings/ecommerce`)
- Amounts are in cents; minimum 100 (\$1.00)

### Step 4: Complete checkout as a shopper

**This step is the user's to perform; the shopper flow is browser-only. Give them `order.checkout_url`, relay the table and requirements below in full, then resume at Step 5 once they confirm the order is placed.**

**Relay the table and the requirements list in full, URLs included whole. Verbatim.** You don't know which card, bank, or phone number the user has, so you can't tell which rows are the edge cases. Don't condense to the path you expect them to take.

Shopper account is separate from the merchant account (same email OK); shopper sign-in is at https://sandbox.dashboard.sezzle.com/customer.

| What | Value |
|---|---|
| OTP (phone + email) | `123123` — always, no notification sent |
| Visa | `4242 4242 4242 4242` (any 3-digit CVC, any future expiry) |
| Mastercard | `5555 5555 5555 4444` |
| Amex | `3714 4963 5398 431` / `3782 8224 6310 005` (4-digit CVC) |
| Discover | `6011 1111 1111 1117` |
| US bank | routing `110000000`, account `000123456789` |
| CAD bank | account `000123456789`, institution `000`, transit `11000` |

Requirements:
- US or CA IP address (VPN otherwise)
- A US or CA phone number (the form validates the format). Nothing is sent to it, so generate one: https://www.bugster.dev/utilities/phone-generator
- Sezzle shows at checkout for carts ~$20–$2500
- Select Pay in Full (preferably) to preserve spending power
- Personal info can be fake; Tax IDs just need the right format

The shopper's last action is clicking **Complete Order**. That redirects to the session's `complete_url`, which may render as a blank or placeholder page, so don't tell the user what they should have seen. Confirm with the Step 5 `GET` rather than taking "it's done" at face value.

### Step 5: Exercise the API

`{order_uuid}` is `order.uuid` from the Step 3 response. Every one of these takes a request body — `release` included.

```
GET  /v2/order/{order_uuid}                 → confirm authorization.approved
POST /v2/order/{order_uuid}/release         → { "amount_in_cents": 1000, "currency": "USD" }
POST /v2/order/{order_uuid}/capture         → { "capture_amount": { "amount_in_cents": 1500, "currency": "USD" } }
POST /v2/order/{order_uuid}/refund          → { "amount_in_cents": 500, "currency": "USD" }
```

**Run them in that order by default; don't ask the user to pick.** All three accept partial amounts, so a partial release, a partial capture of the remainder, then a refund covers every operation on one order. Capturing first leaves nothing to release: release returns the authorization hold, so it only applies to an amount you haven't captured.

When you name the sequence to the user, call it `partial release (optional) → capture → refund`. A bare "release" reads as releasing the whole authorization, and as a prerequisite for capture.

If the user doesn't care about release, do capture → refund and skip it.

As each call returns, say what it proves, using the matching row below. One line, inline, as you go. Step 6's verdict recaps these at the end; it doesn't replace narrating them here.

| Call | What a success proves | What a failure usually means |
|---|---|---|
| `GET /order` → `authorization.approved: true` | Merchant app is approved and the shopper flow completed | Checkout was abandoned or expired (`checkout_status`) |
| `release` | Authorization is live and reversible | Amount exceeds what remains unreleased |
| `capture` | Settlement path is configured | Payout/settlement not set up on the sandbox account |
| `refund` | Refunds are funded | No payment method on the merchant account; add the test bank or switch payout to Delayed Settlement |

Confirm the final state with `GET /v2/order/{order_uuid}`: `authorization` gains `releases[]`, `captures[]`, and `refunds[]` arrays holding each amount. The response also carries shopper name, email, phone, and billing address; don't echo those into summaries.

View results in the sandbox dashboard: https://sandbox.dashboard.sezzle.com/merchant/orders

If the user would rather click through requests themselves than have you run them, Sezzle publishes a Postman collection: https://docs.sezzle.com/docs/api/postman-setup

### Step 6: Report readiness, then use the data or move on

Close with a readiness verdict. Steps 2–5 establish whether this sandbox account can support an integration, so report that per check: pass/fail, plus the blocker for any failure (see the table in Step 5):

```
Sandbox readiness
  Authentication    ✓ token issued
  Merchant approval ✓ keys valid, app approved
  Checkout          ✓ shopper completed, authorization approved
  Release           ✓ authorization reversible
  Capture           ✓ settlement configured
  Refund            ✓ refunds funded
```

A failure here saves the merchant from hitting the same wall mid-integration, when it's much harder to diagnose.

Then offer both of these, explicitly, before you show the handoff table:

1. **Export a summary to a markdown file**: orders created, UUIDs, statuses, amounts. Offer it every time, including when the user looks finished; it's what carries these UUIDs into a later session.
2. Keep prototyping in-session against sandbox.

If they want to build the real integration, this skill stops here. Hand off to the guide that matches the goal:

| Goal | Read |
|---|---|
| Ecommerce checkout, direct REST or JS SDK | https://docs.sezzle.com/docs/guides/direct/introduction |
| Choosing between AUTH and CAPTURE intent | https://docs.sezzle.com/docs/guides/auth-and-capture |
| Event notifications and signature verification | https://docs.sezzle.com/docs/api/core/webhooks/infov2webhooks |
| Subscriptions or recurring charges | https://docs.sezzle.com/docs/api/tokenization/intro |
| Sezzle through Stripe/Braintree/Cybersource | https://docs.sezzle.com/docs/guides/virtual/introduction |
| Checkout link by SMS or email from a register | https://docs.sezzle.com/docs/guides/in-store |
| On-site installment messaging | https://docs.sezzle.com/docs/guides/widgets/sdk (needs a **production** Merchant ID) |
| A prebuilt platform plugin instead of code | https://docs.sezzle.com/docs/plugins/before-you-begin |
| Moving a working integration to production | https://docs.sezzle.com/docs/api/environments |

## Rules

- **Sandbox only.** Base URL `sandbox.gateway.sezzle.com`; keys from `sandbox.dashboard.sezzle.com`. Production checkout URLs contain no "sandbox" — if you don't see it in the URL, STOP: you'd be charged real money.
- **Key format is not an environment signal.** Sandbox and production keys are both `sz_pub_` / `sz_pr_` followed by random characters — there is no prefix, length, or checksum that distinguishes them, so don't invent a credential check. The base URL and the `sandbox.` in the checkout URL are the only reliable signals.
- **Keys are not transferable** between environments.
- **Never print or inline key values.** Reference `$SEZZLE_SANDBOX_PUBLIC_KEY` / `$SEZZLE_SANDBOX_PRIVATE_KEY` in requests and keep them out of scripts you write, command output, and summaries.
- **Only one dashboard session at a time** — logging into sandbox logs you out of production in another tab (and vice versa).
- **Merchant refunds need a payment method** on the sandbox account (use the test bank), or switch payout to Delayed Settlement.
- **Shopper spending power is finite** — release/refund test orders as you go, keep them small, or ask Sezzle to bump the tester's limit.
- **Dynamic widgets are the exception:** they always hit production with a production Merchant ID even on a sandbox store — widget testing is out of scope here.

## Source docs
- https://docs.sezzle.com/docs/api/environments
- https://docs.sezzle.com/docs/api/test-cards
- https://docs.sezzle.com/docs/api/postman-setup
- https://docs.sezzle.com/docs/api/intro
