SDKs

Web

Integrate the Web SDK: secure card fields, saved cards and alternative payment methods.

Tonder's Web SDK (@tonder.io/web-sdk) is a browser TypeScript SDK for accepting payments: secure card fields, new-card and saved-card payments, hosted/3DS presentation, payment-method discovery, transaction lookup, and webhook-friendly transaction responses.

Want an AI agent to do the integration for you? Install the Tonder Web SDK plugin in Claude Code, Claude Desktop, or Codex.

Before you start

You need:

  • Your Tonder public API key. Never put secret keys in browser code.
  • A modern browser: Chrome, Safari, Firefox, or Edge.
  • A server endpoint that can create a short-lived secure_token, if you'll use saved cards / Card-on-File.
  • A webhook endpoint for reliable payment fulfillment.

Install

npm install @tonder.io/web-sdk
import { createTonder, AppError, ErrorKeyEnum } from '@tonder.io/web-sdk';

No bundler? Load the browser global build from the environment CDN:

EnvironmentCDN URL
Stagehttps://zplit-stage.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js
Productionhttps://zplit-prod.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js
<script src="https://zplit-stage.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js"></script>
<script>
  const { createTonder } = window.Tonder;
</script>

In a TypeScript app that uses the CDN, you can install @tonder.io/web-sdk as a devDependency for types only (npm install -D @tonder.io/web-sdk + import type), keeping the runtime on the CDN. Do not import runtime code from the package in that setup.

Quick start: card payment

Add containers for the card fields

<form id="checkout-form">
  <div id="collect-cardholder-name" class="card-field"></div>
  <div id="collect-card-number" class="card-field"></div>
  <div id="collect-expiration-month" class="card-field"></div>
  <div id="collect-expiration-year" class="card-field"></div>
  <div id="collect-cvv" class="card-field"></div>

  <button type="submit">Pay</button>
</form>

Cap each container's height so the secure iframe does not visually grow before it settles into the input layout:

.card-field {
  width: 100%;
  max-height: 90px;
}

Initialize, mount, and pay

import { createTonder } from '@tonder.io/web-sdk';

const tonder = createTonder({
  api_key: 'pk_test_...',
  environment: 'sandbox',
  session: {
    customer: {
      email: 'ada@example.com',
      first_name: 'Ada',
      last_name: 'Lovelace',
    },
  },
});

await tonder.init();

const card_fields = tonder.create('card_fields');

await card_fields.mount();

const transaction = await tonder.pay({
  amount: 150,
  currency: 'MXN',
  return_url: 'https://yourstore.example/checkout/return',
  client_reference: 'order_1001',
  metadata: { cart_id: 'cart_789' },
  payment_method: { type: 'card' },
});

Handle the result

if (transaction.status === 'Success' || transaction.status === 'Authorized') {
  // Show confirmation.
} else if (transaction.status === 'Pending') {
  // The customer may need to complete 3DS or an asynchronous payment method.
  // Confirm final state with webhooks or getTransaction().
} else {
  // Show a recoverable payment message.
  console.warn(transaction.decline_code, transaction.decline_reason);
}

Declines are not thrown as errors — they're returned as transactions: read transaction.status. SDK failures are thrown as AppError (see Errors).

Web SDK amounts are decimal units (150 = MXN 150.00). See Money, currencies, and amounts.

Configuration

createTonder(config) creates one SDK instance for one shopper/session. Recreate the SDK if the customer, secure_token, or environment changes.

FieldRequiredDescription
api_keyYesPublic Tonder key for browser integrations.
environmentYes'sandbox', 'stage', or 'production'.
session.customerFor pay() and saved-card operationsCustomer identity. Omit for read-only return pages that only call getTransaction().
session.secure_tokenFor saved-card operationsShort-lived token minted by your backend.
presentation_modeNo'redirect' by default, or 'embedded' for SDK-owned modal presentation.
events.presentationNoon_open / on_close callbacks for the embedded hosted view.
customization.card_fieldsNoLabels, placeholders, styles, and validation-message overrides for the secure fields.

The full customization tables (per-field styles, error messages, card icon) are in the Web SDK Reference.

Presentation mode

When a payment requires a hosted step (3DS, APM instructions), the SDK uses presentation_mode:

ModeBehavior
redirectThe browser navigates to the hosted page. Use return_url, getTransaction(), and webhooks to confirm final status.
embeddedThe SDK opens a full-screen modal. Card 3DS waits for a final transaction; APM/SPEI hosted instructions may return Pending immediately.

Saved cards (secure_token)

Saved-card operations (getCustomerCards(), enrollCard(), removeCustomerCard(), paying with saved_card) require session.customer and session.secure_token. Mint the token by calling /api/secure-token/ from your backend with your secret key:

fetch("https://stage.tonder.io/api/secure-token/", {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_SECRET_KEY',
    'Content-Type': 'application/json'
  }
})
  .then(response => response.json())
  .then(result => {
    const secureToken = result.access;
    // Pass it to the frontend as session.secure_token
  });
fetch("https://app.tonder.io/api/secure-token/", {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_SECRET_KEY',
    'Content-Type': 'application/json'
  }
})
  .then(response => response.json())
  .then(result => {
    const secureToken = result.access;
  });

The generated secure_token is valid for 1 hour. Use it within that window; if you cache or reuse it after that, mint a new one.

Pay with a saved card

const tonder = createTonder({
  api_key: 'pk_test_...',
  environment: 'sandbox',
  session: {
    customer: { email: 'ada@example.com' },
    secure_token: await getSecureTokenFromYourBackend(),
  },
});

await tonder.init();

const cards = await tonder.getCustomerCards();
const selected_card = cards[0];

// Mount saved-card CVV only when the card cannot be charged through an
// existing Card-on-File subscription.
if (!selected_card.subscription_id) {
  const cvv = tonder.create('card_fields', {
    card_id: selected_card.card_id,
    fields: ['cvv'],
  });

  await cvv.mount();
}

const transaction = await tonder.pay({
  amount: 150,
  currency: 'MXN',
  return_url: 'https://yourstore.example/checkout/return',
  client_reference: 'order_1001',
  payment_method: { type: 'saved_card', card_id: selected_card.card_id },
});

Save a new card

const card_fields = tonder.create('card_fields');

await card_fields.mount();

const enrollment = await tonder.enrollCard();
// { card_id: 'card_123', subscription_id: 'sub_123' }

API reference

POST
/secure-token/

Authorization

SecretKeyAuth
Authorization<token>

Tu SECRET key con prefijo Token , p. ej. Token <SECRET_KEY> — distinta de la API key

In: header

Response Body

application/json

curl -X POST "https://example.com/secure-token/"
{
  "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI3NzI3MTM3LCJpYXQiOjE3Mjc3MjM1MzcsImp0aSI6IjFjZTBkZmExODgwNzQzNGI4MDk2MzdlNTliNmM1NWMzIiwidXNlcl9pZCI6NDYxfQ.DFGNJr7JT6z3cp976PDBT57uX7LaYJLYBsdK8kaSAOI"
}

Alternative payment methods

If your checkout already knows which method to offer, pass the method code directly to pay():

const transaction = await tonder.pay({
  amount: 150,
  currency: 'MXN',
  return_url: 'https://yourstore.example/checkout/return',
  client_reference: 'order_1001',
  payment_method: { type: 'oxxopay' },
});

Use getPaymentMethods() (optional) to render the methods enabled for your business, and getPaymentMethodBanks() for bank-backed SafetyPay methods:

const banks = await tonder.getPaymentMethodBanks();
const bank = banks.cash[0];

const transaction = await tonder.pay({
  amount: 150,
  currency: 'MXN',
  return_url: 'https://yourstore.example/checkout/return',
  client_reference: 'order_1001',
  payment_method: {
    type: 'safetypayCash',
    config: {
      country: bank.country, // e.g. 'Mexico'
      channel: bank.channel, // 'WP' cash, 'OL' transfer
      bank_ids: [{ id: bank.code }], // e.g. [{ id: '8186' }]
    },
  },
});

APM/SPEI methods often settle asynchronously. Use webhooks for fulfillment.

Demos

Try every flow in the SDK demos portal:

FlowDemo
Card paymentweb/card-payment
Card enrollmentweb/enroll-card
Saved cardsweb/saved-cards
Payment methodsweb/payment-methods
SafetyPay banksweb/safetypay-banks

The legacy demos (Web SDK Lite and Inline) use previous SDK versions and are kept for reference only — use the demos above for new integrations. If you are still on the previous SDK, follow the migration guide.

Reconciliation

  • client_reference is required: it's your order reference and appears in dashboards, exports, webhooks, and transaction reports.
  • Use a stable idempotency_key per checkout attempt so retries don't create duplicate charges. Do not reuse client_reference as the idempotency key.
  • Web SDK webhooks use the flat payload (top-level fields) with event_type: payment_Success / payment_Pending. See the events catalog.

Next steps

Was this page helpful?

On this page