---
title: "Breeze 3D Secure Checkout"
description: "Wire your checkout to complete Breeze's required 3D Secure authentication — load the Risk SDK, handle the redirect, and capture the order on return."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.sparkcrm.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Breeze 3D Secure Checkout

Breeze authenticates **every customer card payment** with 3D Secure (3DS) — both new cards and previously saved cards. Unlike traditional gateways (NMI, Authorize.net), a Breeze card charge is not approved silently: the customer completes a short bank authentication step before the payment is captured.

This guide shows how to wire your checkout so Breeze payments complete correctly. It applies **only** to the initial customer purchase — subscription rebills are merchant-initiated and process automatically without any redirect.

> **Prerequisite**: A Breeze gateway configured in Spark CRM with an **API Key** (the only credential the form requires), plus a **Risk SDK Key** and a **Webhook Secret** — both optional in the form, but the Risk SDK Key is needed for the risk session on every card payment, and without the Webhook Secret Spark CRM rejects Breeze’s webhooks. See [Payment Gateways](/payment-processing/gateways#breeze-credentials).

---

## How It Works

A Breeze card checkout is a **redirect flow**, the same shape as PayPal: the customer is sent to Breeze to authenticate, then returns to your funnel to complete the order.

```
Load Risk SDK  → get risk session
    ↓
Process Payment  (card + risk_session_id + return_url + cancel_url)
    ↓
Breeze needs 3DS → response returns redirect_required + redirect_url
    ↓
Redirect the customer to redirect_url  (Breeze's hosted 3DS page)
    ↓
Customer authenticates → Breeze redirects back to your return_url
    ↓
Capture  (finalizes the order)
    ↓
Order complete  → subscriptions, fulfillment, and webhooks fire
```

A backend webhook from Breeze also finalizes the order automatically, so the sale still completes even if the customer closes the browser before returning — as long as the Breeze gateway has a **Webhook Secret** saved in Spark CRM, which is what Spark CRM verifies the webhook against.

---

## Step 1: Load the Risk SDK

On your checkout page, load Breeze's **Risk JS SDK** using your gateway's **Risk SDK Key**. The SDK produces a **risk session ID** that Breeze requires on every card payment for fraud scoring.

Capture the session ID from the SDK and include it when you submit the checkout form.

> The Risk SDK Key is the one you entered on the Breeze gateway — Spark CRM stores it for your checkout page to use and never validates it. If the SDK is not loaded and no `payment.risk_session_id` is sent, the charge is forwarded to Breeze with no risk object and Breeze declines it at the gateway, so check your checkout page first when card payments start failing.

---

## Step 2: Process the Payment

When the customer submits the checkout form, call **Checkout API > Orders > Process Payment** (`POST /checkout/orders/payment`) and include three extra fields alongside the card details:

| Field | Description |
|-------|-------------|
| `payment.risk_session_id` | The risk session ID from the Risk SDK (Step 1) |
| `payment.return_url` | Where Breeze returns the customer after **successful** authentication |
| `payment.cancel_url` | Where Breeze returns the customer after **failed or abandoned** authentication |

Point `return_url` and `cancel_url` at pages on your own funnel — the customer lands there after 3DS, and your success page then calls the Capture endpoint (Step 4).

All three are **required** for a Breeze card payment. If the order is routed directly to a Breeze gateway — you passed `gateway_id`, or the campaign has a Breeze gateway set — and `return_url` or `cancel_url` is missing, Spark CRM rejects the request with a clear `422`: *"This gateway requires 3D Secure. Include payment.return_url and payment.cancel_url…"* Orders routed through a **Payment Orchestrator** are not pre-checked, because the gateway is resolved after this point — there a missing return URL comes back as a Breeze decline ("threeDs is required") instead, so always send both URLs.

---

## Step 3: Handle the Redirect Response

When Breeze needs the customer to authenticate, Process Payment responds with a redirect instruction instead of an immediate approval:

```json
{
  "success": true,
  "message": "Payment initiated - customer redirect required",
  "data": {
"payment_status": "pending_redirect",
"redirect_required": true,
"redirect_url": "https://pay.breeze.cash/hosted-three-ds?d=..."
  }
}
```

When you see `redirect_required: true`, **send the customer's browser to `redirect_url`**. This is Breeze's hosted 3D Secure page, where the customer completes authentication (often instant/"frictionless" for low-risk cards, or a quick bank challenge otherwise).

> Do **not** advance to the upsell or confirmation page yet — the payment is not captured until the customer returns and you call Capture.

---

## Step 4: Confirm the Result (Capture)

After authentication, Breeze redirects the customer back to your `return_url`. On that page, call **Checkout API > Orders > Capture** (`POST /checkout/orders/capture`) with the `order_number`.

Capture re-checks the payment with Breeze and returns one of three results:

| HTTP | Body | What it means |
|------|------|---------------|
| `200` | `success: true` | Approved. The order is completed — subscriptions, fulfillments, and order webhooks fire. Show the success page and continue the funnel. |
| `422` | `success: false`, *"Payment was not completed"* | Declined. Send the customer back to checkout. |
| `202` | `success: true`, `payment_status: "pending_external_confirmation"` | Breeze is reviewing the payment. **Do not treat this as approved** and do not advance to upsells — the webhook completes or declines the order when Breeze resolves it. |

Because a `202` also carries `success: true`, branch on the HTTP status or on `data.payment_status`, not on `success` alone.

If the customer arrives at your `cancel_url` instead (they failed or abandoned 3DS), treat it as a declined payment and return them to the checkout to try again.

### Is calling Capture required?

**Not for the order to complete** — Breeze also sends a `PAYMENT_SUCCEEDED` webhook to Spark CRM that finalizes the order (subscriptions, fulfillments, events) in exactly the same way. The two are **idempotent**: whichever happens first completes the order, and the other becomes a no-op. So an order is **never lost**, even if the customer closes the tab and never returns — **provided the Breeze gateway has a Webhook Secret saved in Spark CRM and the webhook URL is set in your Breeze dashboard**. Without the secret, Spark CRM cannot verify Breeze's webhooks and rejects them with a `403`, leaving your Capture call as the only way an order completes.

The difference is *who gets told the result*:

| | Completes the order | Tells the customer's browser |
|---|---|---|
| **Capture** (you call it on return) | Yes — unless Breeze answers `202` (still reviewing) | **Yes — instantly** |
| **Webhook** (automatic) | Yes | No — it reaches Spark CRM, not the shopper's screen |

A webhook is server-to-server, so it can't tell the shopper's browser anything. That's the one thing Capture is for.

**Recommendation:**

- **Call Capture on return** so the customer immediately sees success/decline and your funnel can advance to upsells.
- **Keep the webhook configured** as the backstop that completes any order where the customer never returns. Make sure your Breeze **webhook URL** is set and that the gateway's **Webhook Secret** is saved in Spark CRM (see [Payment Gateways](/payment-processing/gateways#breeze-credentials)).

> **In short:** the webhook guarantees the order completes; Capture is what shows the customer the result right away. If you don't have upsells or an on-screen confirmation to drive, you can rely on the webhook alone — but only if the Breeze gateway has a **Webhook Secret** saved in Spark CRM, since Spark CRM rejects any Breeze webhook it cannot verify with a `403`. For a standard funnel, call Capture.

---

## Subscriptions & Rebills

3D Secure applies to the **initial** customer purchase only. Once the first payment succeeds, Breeze saves a token for the card. All future **rebills** (subscription renewals) are merchant-initiated and process **automatically with no redirect and no 3DS** — you don't need to do anything for them.

Spark CRM also automatically **chains each rebill to the original authenticated payment** (via the card network's scheme transaction ID), which improves renewal approval rates and strengthens dispute defense — again, nothing to configure.

This is the same "authenticate once, then bill silently" model as PayPal reference transactions.

---

## Sandbox Testing

With **Test Mode** enabled on the Breeze gateway (or a `sk_test_` API key), use Breeze's test cards:

| Scenario | Card number |
|----------|-------------|
| Successful payment | `4000020000000000` |
| Failed payment | `4024007181869214` |
| 3DS challenge | `4010061700000021` |

- Any future expiry date and any 3-digit CVV.
- On the hosted 3DS challenge page, enter the sandbox password **`Checkout1!`**.

---

## Troubleshooting

### "This gateway requires 3D Secure…" (or Breeze "threeDs is required")

Your checkout did not send the 3DS return URLs or the risk session:

- Confirm you are passing `payment.return_url` **and** `payment.cancel_url` on Process Payment — for a Breeze card payment these are required. Spark CRM returns a `422` naming them when the order is routed straight to a Breeze gateway; when it is routed through a Payment Orchestrator you get Breeze's own "threeDs is required" decline instead.
- Confirm the Risk SDK is loaded and you are passing `payment.risk_session_id`.

### Customer redirected but the order never completes

- Make sure your `return_url` page calls **Capture** (`POST /checkout/orders/capture`) with the `order_number`.
- Check that your Breeze **webhook URL** is configured in the Breeze dashboard **and** that the Breeze gateway in Spark CRM has a **Webhook Secret** saved (see [Payment Gateways](/payment-processing/gateways#breeze-credentials)) — the webhook is the backup that completes the order, and without the secret Spark CRM rejects it with a `403`.

### "billingEmail, firstName, and lastName must be present"

Breeze requires the customer's name and a valid email on the first card payment. Ensure the customer record has a first name, last name, and a valid email before processing.

---

## Related Topics

- [Payment Gateways](/payment-processing/gateways) — Configure the Breeze gateway and credentials
- [Basic DTC Checkout Flow](/integrations/dtc-checkout-flow) — The overall checkout, upsell, and completion sequence
- [Alternative Payment Methods](/payment-processing/alternative-payments) — PayPal Wallet, which uses the same redirect-and-capture pattern

Source: https://docs.sparkcrm.io/payment-processing/breeze-3ds-checkout/index.mdx
