Payment Methods

Frictionless SPEI

SPEI without leaving your checkout: the customer pays to a dedicated CLABE and you reconcile it automatically.

Frictionless SPEI automatically processes bank transfers even when they don't match an existing pending transaction. It enables two key scenarios:

  1. Mismatched amounts: the customer deposits a different amount than expected.
  2. Direct transfers: the customer transfers without initiating a checkout.

This feature builds on standard SPEI payments. If you haven't integrated SPEI yet, start with SPEI. It's the recommended option for new SPEI merchants (MX) and is available with API Direct or Hybrid.

CLABE + identifier

Every SPEI deposit uses two identification layers:

  • CLABE (Tonder-managed): a unique 18-digit account assigned by Tonder for each merchant + customer. It's the primary deposit identifier, used for lookups and matching.
  • Identifier fields (you provide): external_id (your internal reference) and additional_external_id (an optional second identifier). They enable your reconciliation logic.

Use case 1: mismatched amount deposits

The customer initiates checkout but deposits a different amount. Example: creates a 100 MXN checkout but deposits 600 MXN. The system matches by CLABE and processes automatically.

{
  "data": {
    "amount": 600.0,
    "metadata": {
      "external_id": "ORD-001",
      "mismatched_deposit": "True",
      "original_expected_amount": "100"
    }
  }
}

Use case 2: direct transfers (no checkout)

The customer transfers directly to their CLABE without initiating a checkout. Example: uses the saved CLABE from a previous deposit and transfers 700 MXN from their banking app. The system finds the last successful transaction for that CLABE, extracts the identifier, and creates a new deposit.

{
  "data": {
    "amount": 700.0,
    "metadata": {
      "external_id": "CUSTOMER-12345",
      "concept": "Frictionless deposit - auto-created"
    }
  }
}

Use the same identifier for both cases — the simplest approach. For example, a gaming platform using player_id:

{
  "metadata": {
    "external_id": "PLAYER-12345"
  }
}

Process the webhook by always crediting the same customer account, and handle mismatched amounts when metadata.mismatched_deposit is "True":

const playerId = webhook.data.metadata.external_id;
await creditPlayer(playerId, webhook.data.amount);

if (webhook.data.metadata.mismatched_deposit === "True") {
  await notifyPlayer(playerId, "amount_mismatch");
}

Advanced setup (dual identifiers)

For merchants who need different identifiers per use case — for example, order-level tracking for checkouts (use case 1) and customer-level tracking for direct transfers (use case 2):

{
  "amount": 500,
  "currency": "MXN",
  "payment_method": "spei",
  "metadata": {
    "external_id": "ORDER-12345",
    "additional_external_id": "PLAYER-98765"
  }
}

Use case 1 webhook (a pending transaction exists):

{
  "metadata": {
    "external_id": "ORDER-12345",
    "additional_external_id": "PLAYER-98765",
    "mismatched_deposit": "True"
  }
}
const orderId = webhook.data.metadata.external_id;
await completeOrder(orderId, webhook.data.amount);

Use case 2 webhook (no pending transaction; external_id isn't included since no order exists):

{
  "metadata": {
    "additional_external_id": "PLAYER-98765"
  }
}
const playerId = webhook.data.metadata.additional_external_id;
await manualTopUp(playerId, webhook.data.amount);
StrategyUse case 1 returnsUse case 2 returnsComplexityBest for
Single identifierplayer_idplayer_id (history)SimpleCustomer-centric platforms
Dual identifiersorder_idplayer_id (history)AdvancedOrder + customer tracking

Custom field aliases

Instead of always using external_id / additional_external_id, you can ask Tonder to use domain-specific field names instead (for example order_id / player_id):

{
  "field_aliases": {
    "order_id": "external_id",
    "player_id": "additional_external_id"
  }
}

With that configuration, your request and the webhook use your own field names:

{
  "metadata": {
    "order_id": "ORDER-12345",
    "player_id": "PLAYER-98765"
  }
}

Dual identifiers and custom field aliases are agreed on and configured together with your Tonder integration manager. Start with a single identifier; add dual identifiers only if you need different tracking per use case, and aliases only if you want domain-specific field names.

Check a deposit's status (Polling)

Webhooks are the recommended way to receive Frictionless SPEI deposit updates, but you can also query the status directly with the deposit-specific endpoints. This is useful for reconciliation or when a webhook delivery is delayed.

GET https://02ljs5zoif.execute-api.us-east-1.amazonaws.com/stage/api/v1/deposits/{id}/transaction
GET https://38dictnz7c.execute-api.us-east-1.amazonaws.com/pdn/api/v1/deposits/{transaction_id}/transaction

These endpoints are specific to Frictionless SPEI deposits and are distinct from the general Get Transaction Status. Use {transaction_id} / {id} as the deposit's transaction identifier.

Simulator (Sandbox)

In sandbox you can simulate a Frictionless SPEI deposit — including mismatched amounts and direct transfers — from the simulator:

https://tonder.live/simulatedeposits/

The simulator is Sandbox-only. Use it to test CLABE matching and your reconciliation logic before going to production.

Next steps

Was this page helpful?

On this page