Direct Payment Integration Guide

Direct Payment Integration Guide

Overview

Direct Payment is Breeze's server-to-server (S2S) card payment API. Instead of redirecting customers to a hosted checkout page, you collect card details in your own checkout UI and submit the payment through Breeze.

This guide is for merchants who want:

  • Full control over the checkout experience
  • A custom, white-labeled payment form
  • Real-time authorization without page redirects

Key difference from Hosted Payment Pages: with Direct Payment your backend drives the payment request and the customer never leaves your UI.

Card data & PCI. You submit card details to POST /v1/gateway/payments on the secure.breeze.cash host. Your PCI obligations depend on how you collect those fields: to minimize your own scope, capture them with a PCI-compliant tokenization provider rather than handling raw card numbers on your servers; if you do handle raw card data, you are responsible for your own PCI compliance. Confirm the exact card-collection and PCI setup with your Breeze onboarding contact.


Prerequisites

  1. A Breeze merchant account — with sandbox and/or production enabled.
  2. An API keysk_test_… (sandbox) or sk_live_… (production), from Dashboard → Developer → API Keys.
  3. The Breeze Risk JS SDK — loaded on your checkout page to generate a risk.sessionId (required for customer-initiated payments; see Risk).
  4. HTTPS on your domain — required for card handling and Apple Pay / Google Pay.
  5. PCI handling — see the card-data note above: your obligations depend on how you collect card fields. Using a PCI-compliant tokenization provider minimizes your scope; handling raw card data means you are responsible for your own PCI compliance. Confirm specifics with your Breeze onboarding contact.

1. Load the Breeze Risk JS SDK

Customer-initiated (CIT) payments require a risk.sessionId generated by the Breeze Risk JS SDK on your checkout page. (Merchant-initiated transactions — see §5 — do not require it.)

Follow the official Risk JS SDK guide for the exact API. Load the SDK, start a session, and forward the resulting session id to your backend to send as risk.sessionId:

<script src="https://cdn.jsdelivr.net/gh/beamo-co/js@main/breeze.js"></script>
<!-- Initialize per the Risk JS SDK guide (client.initSession()), then read
     the session id and forward it to your backend as risk.sessionId. -->

2. Collect Card Details

Tokenization: per the card-data note in the Overview, collect card fields with a PCI-compliant tokenization provider so raw PAN never touches your servers. The plain form fields below show only which values a card payment needs — not the production collection method.

A RAW_PAN card payment needs:

  • Card number (PAN)
  • Expiry month (MM) and year (YYYY)
  • CVV (3–4 digits) — required for customer-initiated RAW_PAN
  • Customer name + email (for anonymous customers; see Customer identification)
  • Billing address (optional; improves fraud scoring)

3. Submit the Payment (Backend)

Call POST /v1/gateway/payments.

Use the correct host:

EnvironmentHost
Sandboxhttps://secure-sandbox.breeze.cash
Productionhttps://secure.breeze.cash

The GET status and 3DS-resume endpoints use https://api.breeze.cash instead (see §4 and Merchant-provided 3DS). Authentication is HTTP Basic: the API key as the username, an empty password (Authorization: Basic base64("sk_live_…:")).

Required for card CITs: a customer-initiated RAW_PAN, NETWORK_TOKEN, Google Pay PAN_ONLY, or SAVED_CARD payment must include a threeDs block — either BREEZE_HOSTED (Breeze runs the 3DS challenge) or MERCHANT_PROVIDED. Omitting it is rejected. Wallet methods that carry their own network cryptogram, and all MIT flows, don't need it.

Node.js

const axios = require('axios');

async function processPayment(req, res) {
  const { amount, currency, sessionId, card, billingEmail, cardholderName } = req.body;

  const baseUrl =
    process.env.BREEZE_ENV === 'production'
      ? 'https://secure.breeze.cash'
      : 'https://secure-sandbox.breeze.cash';
  const apiKey = process.env.BREEZE_API_KEY; // sk_live_… or sk_test_…

  try {
    const { data } = await axios.post(
      `${baseUrl}/v1/gateway/payments`,
      {
        amount, // minor units, e.g. 1000 = $10.00
        currency, // e.g. "USD"
        clientReferenceId: `order_${req.body.orderId}`, // unique dedup key, matches [A-Za-z0-9_-]+
        customer: {
          email: billingEmail,
          firstName: cardholderName.split(' ')[0],
          lastName: cardholderName.split(' ').slice(1).join(' '),
        },
        paymentMethod: {
          type: 'RAW_PAN',
          card: {
            number: card.number, // tokenized in transit by the PCI-vault domain
            expiryMonth: card.expiryMonth, // "12"
            expiryYear: card.expiryYear, // "2028"
            cvv: card.cvv, // required for customer-initiated RAW_PAN
          },
        },
        transactionContext: { initiator: 'CUSTOMER' },
        // Required for a customer-initiated card payment:
        threeDs: {
          mode: 'BREEZE_HOSTED',
          successReturnUrl: 'https://yourstore.com/checkout/3ds-complete',
          failReturnUrl: 'https://yourstore.com/checkout/3ds-failed',
        },
        risk: { sessionId }, // from the Risk JS SDK; required for CIT
      },
      { auth: { username: apiKey, password: '' } } // Basic auth, empty password
    );

    // Envelope: data.status is the API-call result; data.data.status is the payment result.
    const payment = data.data;
    return res.json(handlePaymentResult(payment));
  } catch (error) {
    console.error('Payment error:', error.response?.data || error.message);
    return res.status(500).json({ error: 'Payment processing failed' });
  }
}

Python

import os
import requests

def process_payment(amount, currency, session_id, card, cardholder_name, billing_email, order_id):
    base_url = (
        'https://secure.breeze.cash'
        if os.getenv('BREEZE_ENV') == 'production'
        else 'https://secure-sandbox.breeze.cash'
    )
    api_key = os.getenv('BREEZE_API_KEY')

    payload = {
        'amount': amount,  # minor units
        'currency': currency,
        'clientReferenceId': f'order_{order_id}',  # unique dedup key
        'customer': {
            'email': billing_email,
            'firstName': cardholder_name.split()[0],
            'lastName': ' '.join(cardholder_name.split()[1:]),
        },
        'paymentMethod': {
            'type': 'RAW_PAN',
            'card': {
                'number': card['number'],  # tokenized in transit by the PCI-vault domain
                'expiryMonth': card['expiryMonth'],
                'expiryYear': card['expiryYear'],
                'cvv': card['cvv'],
            },
        },
        'transactionContext': {'initiator': 'CUSTOMER'},
        'threeDs': {
            'mode': 'BREEZE_HOSTED',
            'successReturnUrl': 'https://yourstore.com/checkout/3ds-complete',
            'failReturnUrl': 'https://yourstore.com/checkout/3ds-failed',
        },
        'risk': {'sessionId': session_id},
    }

    resp = requests.post(f'{base_url}/v1/gateway/payments', json=payload, auth=(api_key, ''))
    body = resp.json()
    if body.get('status') != 'SUCCEEDED':
        return {'success': False, 'error': body.get('errorMessage'), 'errorCode': body.get('errorCode')}
    return body['data']  # inspect data.status

Customer identification

Provide the customer in one of three ways:

ModeSendNotes
Existing Breeze customercustomer.id (cus_*)Other fields act as updates if supplied.
Merchant referencecustomer.referenceIdBreeze creates or retrieves a cus_* keyed on it.
Anonymous shoppercustomer.email + customer.firstName + customer.lastName (all required; names paired)Not allowed when saving a card.

Saving a card (storeFutureUsage) and charging a SAVED_CARD both require customer.id or customer.referenceId — anonymous customers can't own a saved card.

Merchant-supplied tax (optional)

If you calculate tax yourself, declare it with taxDetails. The tax is included in the top-level amount (tax-inclusive) — Breeze does not add it on top. As Merchant of Record, Breeze then remits and files the tax you report. taxDetails is optional, but when you send it, every field below is required.

FieldTypeNotes
taxDetails.amountintegerTax portion of the tax-inclusive amount, in minor units. Must satisfy 0 ≤ taxDetails.amount < amount.
taxDetails.modestringmerchant_calculated only — you compute the tax; Breeze remits and files it.
taxDetails.location.countrystringISO 3166-1 alpha-2 country code of the tax jurisdiction.
taxDetails.location.postalCodestringZIP / postal code used to resolve the jurisdiction.

You assert the jurisdiction explicitly via taxDetails.location — there is no fallback to customer.address. The payment response and webhooks echo taxDetails back.

"taxDetails": {
  "amount": 80,
  "mode": "merchant_calculated",
  "location": { "country": "US", "postalCode": "10001" }
}

Tax is inclusive. For a $10.00 charge that already includes $0.80 of tax, send amount: 1000 with taxDetails.amount: 80 — not 1080. The pre-tax portion (amount − taxDetails.amount) must stay positive, so tax can never be the entire charge.


4. Handle the Response

Every response is wrapped in an envelope. Always read the inner data.status for the payment outcome — the outer status only says the API call was processed.

{
  "status": "SUCCEEDED",
  "data": {
    "pageId": "page_abc123xyz",
    "paymentId": "py_def456uvw",
    "status": "PENDING_CAPTURE",
    "amount": 1000,
    "currency": "USD",
    "schemeTransactionId": "0123456789abcdef",
    "customerId": "cus_xyz789"
  }
}

The synchronous data.status on a successful card authorization is PENDING_CAPTURE (auto-capture: OPEN → PENDING_CAPTURE → CONFIRMED). CONFIRMED is delivered later via the PAYMENT_ATTEMPT_CAPTURED webhook — don't wait for it inline.

Possible synchronous data.status values: PENDING_CAPTURE (authorized), ACTION_REQUIRED (3DS needed), FAILED (declined). Handle them:

function handlePaymentResult(payment) {
  switch (payment.status) {
    case 'PENDING_CAPTURE':
    case 'CONFIRMED': // if capture already completed synchronously
      // Authorized. Persist pageId + schemeTransactionId; fulfil on PAYMENT_ATTEMPT_CAPTURED.
      return { success: true, pageId: payment.pageId };

    case 'ACTION_REQUIRED':
      // 3DS. For BREEZE_HOSTED, redirect the shopper to nextAction.redirectUrl.
      return { success: false, action: '3ds', redirectUrl: payment.nextAction?.redirectUrl };

    case 'FAILED':
      return {
        success: false,
        errorCode: payment.errorCode,
        reason: payment.failureReason,
        advice: payment.adviceCode,
      };

    default:
      return { success: false, status: payment.status };
  }
}

3DS challenge (ACTION_REQUIRED, BREEZE_HOSTED):

{
  "status": "SUCCEEDED",
  "data": {
    "pageId": "page_abc123xyz",
    "paymentId": "py_def456uvw",
    "status": "ACTION_REQUIRED",
    "nextAction": { "type": "REDIRECT_TO_URL", "redirectUrl": "https://…/3ds/xyz" }
  }
}

Redirect the shopper to nextAction.redirectUrl. After the challenge, Breeze returns them to your successReturnUrl / failReturnUrl; confirm the final state via GET /v1/gateway/payments/{pageId} or the PAYMENT_ATTEMPT_CAPTURED webhook.

Declined (FAILED):

{
  "status": "SUCCEEDED",
  "data": {
    "pageId": "page_abc123xyz",
    "paymentId": "py_def456uvw",
    "status": "FAILED",
    "amount": 1000,
    "currency": "USD",
    "errorCode": "ISSUER_DECLINE",
    "failureReason": "The issuing bank declined the transaction.",
    "adviceCode": "try_again_later"
  }
}

adviceCode is one of try_again_later, do_not_try_again, confirm_card_data (when present). Surface failureReason to the shopper; use adviceCode to decide whether to prompt a retry or a different card.

Merchant-provided 3DS

If you run 3DS on your own infrastructure, send threeDs: { mode: "MERCHANT_PROVIDED", … } on create. When Breeze needs the authentication result it responds with:

{ "status": "SUCCEEDED", "data": { "status": "ACTION_REQUIRED", "nextAction": { "type": "AUTHENTICATION_REQUIRED" } } }

Complete 3DS, then resume the payment with your results:

POST https://api.breeze.cash/v1/gateway/payments/{pageId}/authentications
{
  "threeDs": {
    "eci": "05",
    "authenticationValue": "<base64 CAVV/AAV>",
    "dsTransactionId": "<UUID>",
    "version": "2.2.0"
  }
}

All four threeDs fields are required. The response is the updated payment (same envelope + shape).


5. Merchant-Initiated Transactions (MIT)

For recurring billing, installments, or unscheduled debits, set transactionContext.initiator to MERCHANT. MIT flows skip 3DS and do not require a risk block (there's no client session to run the Risk SDK).

{
  "transactionContext": {
    "initiator": "MERCHANT",
    "mitType": "SUBSCRIPTION",
    "originalTransaction": { "schemeTransactionId": "0123456789abcdef" }
  }
}
  • mitType — required for MIT: SUBSCRIPTION, INSTALLMENT, or UNSCHEDULED.
  • originalTransaction.schemeTransactionIdoptional but strongly recommended: the schemeTransactionId from the original CIT. It materially improves authorization rates and supports dispute defense. Persist it from the first payment's response/webhook. Omit it only for chains that started outside Breeze (e.g. migrating recurring billing from another processor) — don't drop it just because it's allowed.

6. Webhooks

Breeze POSTs webhooks to your configured endpoint. For Direct Payment, PAYMENT_ATTEMPT_AUTHORIZED and PAYMENT_ATTEMPT_CAPTURED are sent on every attempt, and PAYMENT_ATTEMPT_FAILED is sent by default:

EventMeaning
PAYMENT_ATTEMPT_AUTHORIZEDAuthorized, not yet captured (PENDING_CAPTURE).
PAYMENT_ATTEMPT_CAPTUREDCaptured — funds on the way (CONFIRMED).
PAYMENT_ATTEMPT_FAILEDAuthorization or capture failed.
{
  "type": "PAYMENT_ATTEMPT_CAPTURED",
  "data": {
    "pageId": "page_abc123xyz",
    "paymentId": "py_def456uvw",
    "status": "CONFIRMED",
    "amount": 1000,
    "currency": "USD",
    "clientReferenceId": "order_1234",
    "schemeTransactionId": "0123456789abcdef",
    "savedPaymentMethodId": "saved_abc456",
    "customer": { "id": "cus_xyz789", "referenceId": "ref_123", "email": "[email protected]" },
    "payinDetails": { "amount": 1000, "grossAmount": 1000, "currency": "USD", "scheme": "VISA", "last4": "4242" }
  },
  "signature": "<HMAC-SHA256 of data>"
}

savedPaymentMethodId appears when the card was saved; customer when one is linked. Always verify the signature before processing — see the Webhook Signature Verification guide.


7. Testing in Sandbox

Use https://secure-sandbox.breeze.cash/v1/gateway/payments with a sandbox API key (never mix with live).

Soft decline — set the cardholder name so firstName + lastName equals SOFT_DECLINED (firstName: "SOFT_", lastName: "DECLINED"). Any test card with that name returns data.status: "FAILED" with errorCode: "ISSUER_DECLINE", adviceCode: "try_again_later".

3DS reject — card number 4264281511112228, only when threeDs.mode: "BREEZE_HOSTED". Breeze's hosted 3DS page rejects the challenge and the payment lands in data.status: "FAILED" with a 3DS-related errorCode.

The decline name is read from customer.firstName / customer.lastName on the request — there is no separate billingContact field; the gateway maps the customer name into the cardholder name it matches against. These magic values are provider-dependent, and only the published values are supported sandbox contracts; for other scenarios (specific refusal codes, insufficient-funds vs fraud), tag a Breeze team member.


8. Checkout Display Requirements

Because Breeze is the Merchant of Record, your checkout must present the following on the same page as the pay action, within your own UI, before the customer pays:

  1. Breeze's Terms & Conditions (and Privacy Policy) — Breeze provides the current copy and URLs during onboarding; use those rather than writing your own.
  2. Product information — including price and a clear description of what's being purchased.
  3. A refund policy — your own return/refund policy, visible to the customer.
  4. A purchase-finality notice below the pay button — for example:

    This is a one-time addition to your order. One-click purchases are final sale and non-refundable. View our [Return Policy].

Contact your Breeze onboarding representative for the current Terms/Privacy copy, the Merchant-of-Record disclosure wording, and the canonical URLs.


9. Receipts

By default, Breeze sends a branded receipt (from [email protected]) to customer.email after a successful capture. Failed/declined payments get no receipt.

Merchant-managed receipts are a gated opt-in (not the default). To send your own, your receipt must clearly:

  • State that the purchase was made from Breeze, and
  • Show how the transaction appears on the customer's card statement (e.g. Breeze*merchantName).

You must also BCC {pageId}@inbound.breeze.com on every receipt you send, where {pageId} is the page_* id returned as data.pageId for the payment. Once your template is ready, send a test — Breeze reviews it and, once cleared, enables merchant-managed receipts for your account (which also stops Breeze from sending its own receipt to the customer).


10. Common Integration Patterns

Save a card for future use

Set paymentMethod.storeFutureUsage (a string enum: on_session or off_session). Requires customer.id or customer.referenceId.

await axios.post(`${baseUrl}/v1/gateway/payments`, {
  amount: 1000,
  currency: 'USD',
  clientReferenceId: `order_${orderId}`,
  customer: { id: 'cus_xyz123' }, // id or referenceId required to save
  paymentMethod: {
    type: 'RAW_PAN',
    card: { number: card.number, expiryMonth: '12', expiryYear: '2028', cvv: card.cvv },
    storeFutureUsage: 'off_session', // save for later merchant-initiated reuse
  },
  transactionContext: { initiator: 'CUSTOMER' },
  threeDs: { mode: 'BREEZE_HOSTED', successReturnUrl, failReturnUrl }, // required (CIT)
  risk: { sessionId },
}, { auth });

// Response returns the saved-method id:
//   data.savedPaymentMethodId === "saved_abc456"   // prefix is "saved_", not "spm_"

Charge a saved card (MIT subscription renewal)

SAVED_CARD nests the id under card. MIT skips 3DS and doesn't need risk.

await axios.post(`${baseUrl}/v1/gateway/payments`, {
  amount: 1000,
  currency: 'USD',
  clientReferenceId: `subscription_renewal_${cycleId}`,
  customer: { id: 'cus_xyz123' }, // must identify the owning customer
  paymentMethod: {
    type: 'SAVED_CARD',
    card: { savedPaymentMethodId: 'saved_abc456' }, // nested under card
  },
  transactionContext: {
    initiator: 'MERCHANT',
    mitType: 'SUBSCRIPTION',
    originalTransaction: { schemeTransactionId: '0123456789abcdef' }, // from the first CIT (optional)
  },
  // no threeDs, no risk needed for MIT
}, { auth });

Installments (first CIT, then MIT)

// 1) First payment — CIT that also saves the card.
const first = await axios.post(`${baseUrl}/v1/gateway/payments`, {
  amount: 2500,
  currency: 'USD',
  clientReferenceId: `installment_${orderId}_1of4`,
  customer: { id: 'cus_xyz123' },
  paymentMethod: {
    type: 'RAW_PAN',
    card: { number: card.number, expiryMonth: '12', expiryYear: '2028', cvv: card.cvv },
    storeFutureUsage: 'off_session', // needed to get a saved_ id back
  },
  transactionContext: { initiator: 'CUSTOMER' },
  threeDs: { mode: 'BREEZE_HOSTED', successReturnUrl, failReturnUrl },
  risk: { sessionId },
}, { auth });

const savedId = first.data.data.savedPaymentMethodId; // "saved_…"
const schemeTxnId = first.data.data.schemeTransactionId;

// 2) Later installments — MIT on the saved card.
await axios.post(`${baseUrl}/v1/gateway/payments`, {
  amount: 2500,
  currency: 'USD',
  clientReferenceId: `installment_${orderId}_2of4`,
  customer: { id: 'cus_xyz123' },
  paymentMethod: { type: 'SAVED_CARD', card: { savedPaymentMethodId: savedId } },
  transactionContext: {
    initiator: 'MERCHANT',
    mitType: 'INSTALLMENT',
    originalTransaction: { schemeTransactionId: schemeTxnId },
  },
}, { auth });

List / remove saved cards

  • GET https://api.breeze.cash/v1/gateway/customers/{customerId}/payment-methods?type=card — active saved cards (each carries display metadata + the saved_* id).
  • DELETE https://api.breeze.cash/v1/gateway/customers/{customerId}/payment-methods/{savedPaymentMethodId} — soft-delete (deactivate) a saved card.

11. Error Handling & Debugging

The HTTP status tells you whether the API call was accepted; data.status / data.errorCode tell you the payment outcome.

try {
  const { data } = await axios.post(`${baseUrl}/v1/gateway/payments`, payload, { auth });
  const payment = data.data;
  // switch on payment.status — see §4
} catch (error) {
  const s = error.response?.status;
  if (s === 401) console.error('Auth failed — check API key and host');
  else if (s === 400) console.error('Bad request', error.response.data); // { status:"FAILED", errorCode, errorMessage }
  else if (s === 409) console.error('Lock contention — safe to retry with the same clientReferenceId');
}

Common issues

  • 401 Unauthorized — API key/host mismatch, wrong livemode, or a request that didn't come through the required proxy.
  • 400 Bad Request — invalid payload. For a CIT card payment, check the usually-missed ones: threeDs (required for CIT), risk.sessionId (required for CIT), correct paymentMethod shape.
  • 409 — lock contention; retry with the same clientReferenceId (idempotent).
  • Wrong host — create must use secure.breeze.cash; status/resume use api.breeze.cash.

Required fields depend on the flow: amount, currency, clientReferenceId, customer, paymentMethod are always required; customer.email/firstName/lastName only for anonymous customers; risk.sessionId and threeDs for CIT card payments.

See the Error Reference for errorCode meanings.


12. Next Steps

  1. Set up webhooks at Dashboard → Developer → Webhooks and verify signatures; listen for PAYMENT_ATTEMPT_CAPTURED and PAYMENT_ATTEMPT_FAILED.
  2. Confirm your PCI / tokenization setup with onboarding (see the Overview note).
  3. Test end-to-end in sandbox.
  4. Go live — swap sandbox credentials for production and switch the host to https://secure.breeze.cash.

For questions, email [email protected] or tag a Breeze team member. (Whitelist the breeze.cash domain for Breeze emails.)


13. Wallets & Network Tokens

Direct Payment accepts tokenized wallet payments. You decrypt the wallet token on your side and submit the derived card/token fields.

Supported paymentMethod.type values

  • RAW_PAN — manual card entry.
  • DECRYPTED_APPLE_PAY — Apple Pay; DPAN + TAVV cryptogram.
  • DECRYPTED_GOOGLE_PAY — Google Pay; PAN_ONLY (FPAN) or CRYPTOGRAM_3DS (DPAN).
  • NETWORK_TOKEN — merchant-provisioned network token (BYOT).
  • SAVED_CARD — a previously stored card (saved_*).

Apple Pay — no CVV; the cryptogram is required for CIT, and wallet auth means no threeDs block is needed:

paymentMethod: {
  type: 'DECRYPTED_APPLE_PAY',
  card: { number: dpan, expiryMonth: '12', expiryYear: '2028' }, // no cvv on wallets
  network: { cryptogram: '<base64 TAVV>', eci: '05' }, // required for CIT
}

Google Pay — send network.tokenType; no CVV. For CRYPTOGRAM_3DS include cryptogram + eci (no threeDs needed); for PAN_ONLY there's no cryptogram and a CIT does require a threeDs block:

// CRYPTOGRAM_3DS (DPAN)
paymentMethod: {
  type: 'DECRYPTED_GOOGLE_PAY',
  card: { number: dpan, expiryMonth: '12', expiryYear: '2028' },
  network: { tokenType: 'CRYPTOGRAM_3DS', cryptogram: '<base64 CAVV>', eci: '05' },
}

// PAN_ONLY (FPAN) — also send threeDs on a CIT
paymentMethod: {
  type: 'DECRYPTED_GOOGLE_PAY',
  card: { number: fpan, expiryMonth: '12', expiryYear: '2028' },
  network: { tokenType: 'PAN_ONLY' },
}

Apple Pay on the web inside an iframe requires additional domain certification: you host a domain-association file (provided by Breeze's integration team) on your domain. Reach out to Breeze to set this up.


14. Risk Considerations

risk.sessionId (from the Risk JS SDK) is required for customer-initiated (CIT) payments and optional/omitted for MIT. You can improve fraud scoring with optional signals:

risk: {
  sessionId,               // required for CIT; omit for MIT
  ipAddress: '203.0.113.45',
  userAgent: 'Mozilla/5.0…',
  userAgentClientHint: '"Chrome"; v="120"',
  acceptHeader: 'text/html,application/xhtml+xml,…',
  language: 'en-US',
  timezone: '-480',        // string; minutes offset from UTC (UTC+8 = "-480")
  screenWidth: 1920,
  screenHeight: 1080,
  colorDepth: 24,
  javaEnabled: false,
  javascriptEnabled: true,
  timestamp: 1705340400000,
}

Best practices

  • Always include sessionId for CIT — it's the primary signal.
  • Pass ipAddress from your server, not the client.
  • timezone is a string matching -?\d{1,4} (minutes). Note -new Date().getTimezoneOffset() returns a number and, for UTC+8, 480 — serialize as the string "-480" if you want the UTC-offset convention shown here.
  • Send timestamp as close to checkout as possible.

15. Reconciliation

Match Direct Payment transactions to your orders by clientReferenceId and pageId.

IdempotencyclientReferenceId is your dedup key. Re-submitting the same key returns the same payment (same pageId), so safe retries never double-charge.

// A network timeout on the first attempt → retry with the SAME clientReferenceId.
const a = await axios.post(url, { clientReferenceId, ...rest }, { auth });
const b = await axios.post(url, { clientReferenceId, ...rest }, { auth });
// a.data.data.pageId === b.data.data.pageId

Status truth — the synchronous POST returns PENDING_CAPTURE; PAYMENT_ATTEMPT_CAPTURED (→ CONFIRMED) arrives afterward. Both the webhook and GET /v1/gateway/payments/{pageId} are authoritative; trust the most recent status.

Monthly — reconcile your records against a Breeze statement export from the dashboard (Reports → Statements), matching on clientReferenceId or pageId, and flag any Breeze transactions missing from your records.


Did this page help you?