Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
"es/guides/postman-collections"
]
},
{
"group": "Sandbox",
"pages": [
"es/sandbox/introduction",
"es/sandbox/authentication",
"es/sandbox/scenarios",
"es/sandbox/cash-in",
"es/sandbox/cash-out",
"es/sandbox/webhooks"
]
},
{
"group": "Webhooks",
"pages": [
Expand Down Expand Up @@ -92,6 +103,17 @@
"en/guides/postman-collections"
]
},
{
"group": "Sandbox",
"pages": [
"en/sandbox/introduction",
"en/sandbox/authentication",
"en/sandbox/scenarios",
"en/sandbox/cash-in",
"en/sandbox/cash-out",
"en/sandbox/webhooks"
]
},
{
"group": "Webhooks",
"pages": [
Expand Down Expand Up @@ -138,6 +160,17 @@
"pt-br/guides/postman-collections"
]
},
{
"group": "Sandbox",
"pages": [
"pt-br/sandbox/introduction",
"pt-br/sandbox/authentication",
"pt-br/sandbox/scenarios",
"pt-br/sandbox/cash-in",
"pt-br/sandbox/cash-out",
"pt-br/sandbox/webhooks"
]
},
{
"group": "Eventos de Webhook",
"pages": [
Expand Down
81 changes: 81 additions & 0 deletions en/sandbox/authentication.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
title: 'Authentication'
description: 'Authentication in sandbox is structurally identical to production.'
mode: 'wide'
---

## Overview

Sandbox authentication uses the same two layers as production:

1. **X.509 certificate (mTLS)** — issued by NTX Pay at onboarding.
2. **OAuth 2.0 `client_credentials`** — `clientId` + `clientSecret` received during signup.

Together they return a **JWT** (10-minute validity) used on the remaining endpoints as `Authorization: Bearer ...`.

<Info>
Sandbox credentials are **distinct** from production. If you use production credentials against `https://sandbox.ntxpay.com`, you will get `401`. The HTTP contract is identical — what changes is the certificate + clientId/clientSecret pair.
</Info>

## Get a Token

### POST /api/auth/token

```bash
curl -X POST https://sandbox.ntxpay.com/api/auth/token \
-H "X-SSL-Client-Cert: $ENCODED_CERT" \
-H "Content-Type: application/json" \
-d '{
"clientId": "qr-93-550e8400",
"clientSecret": "a1b2c3d4e5f6g7h8"
}'
```

#### Response (201)

```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 600,
"scope": "email profile"
}
```

## Use the Token

On a sandbox account, any authenticated call simulates the full pipeline without moving real money:

```bash
curl -X POST https://sandbox.ntxpay.com/api/spei/cash-out \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amountCentavos": 15000,
"destinationClabe": "012180001234567890",
"beneficiaryName": "Maria Lopez",
"externalId": "test-001"
}'
```

The response is always `201 Created` with `status: PENDING`. The final outcome (confirmation or failure) arrives via webhook ~1 second later. See [Scenarios](/en/sandbox/scenarios) to force specific outcomes.

## Renewal

The token expires in **10 minutes (600s)**. There is no refresh token — get a new one via `POST /api/auth/token` before expiration.

<Warning>
Under high load, mint a token per worker and renew every ~8 minutes to avoid `401` due to expiration.
</Warning>

## Common Errors

| Code | Cause | Fix |
|---|---|---|
| `400` | `X-SSL-Client-Cert` missing | Configure NGINX/ALB to forward the certificate |
| `401` | Invalid `clientId`/`clientSecret` | Double-check credentials; confirm you are using the sandbox ones |
| `401` | Certificate expired/revoked | Request renewal from NTX Pay |

## Detailed documentation

For the full step-by-step (certificate encoding, examples in multiple languages, etc.) see [Authentication](/en/guides/authentication) in the main guide — the only difference is the base URL `https://sandbox.ntxpay.com`.
88 changes: 88 additions & 0 deletions en/sandbox/cash-in.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: 'Cash-in (SPEI receive)'
description: 'How to generate a disposable cash-in CLABE in sandbox.'
mode: 'wide'
---

## What it does

`POST /api/spei/cash-in` generates a **disposable CLABE** bound to your sandbox account. Any SPEI transfer received at that CLABE triggers a `cash_in` webhook to the configured URL.

In sandbox, confirmation is **simulated** ~1 second after CLABE creation (instead of waiting for a real transfer). This lets you test the entire cash-in flow without depending on a real issuing bank.

## Example

```bash
curl -X POST https://sandbox.ntxpay.com/api/spei/cash-in \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amountCentavos": 50000,
"externalId": "order-001",
"customerName": "Juan Pérez"
}'
```

### Response (201)

```json
{
"id": 12345,
"externalId": "order-001",
"status": "PENDING",
"amountCentavos": 50000,
"clabe": "646180123456789012",
"expiresAt": "2026-03-26T10:30:00.000Z"
}
```

Use the returned `clabe` to display to the end payer (your company's customer). In sandbox this CLABE is fictitious, but the `transaction.clabe` field arriving in the webhook will be **the same**.

## Expected webhook

After ~1 second (default `success` scenario):

```json
{
"event": "cash_in",
"deliveryId": "...",
"transaction": {
"id": 12345,
"externalId": "order-001",
"status": "CONFIRMED",
"amountCentavos": 50000,
"clabe": "646180123456789012",
"confirmedAt": "2026-03-26T10:00:01.000Z",
"counterpart": {
"name": "Simulated Payer",
"taxId": "PAGS850101ABC",
"bank": {
"code": "012",
"name": "BBVA México"
}
}
}
}
```

## Test scenarios

| Scenario | Webhook |
|---|---|
| Default | `CONFIRMED` |
| `error:invalid-clabe` | `FAILED` |
| `error:duplicate-external-id` | `FAILED` |
| `delayed:5s` | `CONFIRMED` after 5s |

See [Scenarios](/en/sandbox/scenarios) for the full list.

## Next steps

<CardGroup cols={2}>
<Card title="Cash-out" href="/en/sandbox/cash-out">
How to send SPEI in sandbox.
</Card>
<Card title="Webhooks" href="/en/sandbox/webhooks">
Understanding webhook delivery in sandbox.
</Card>
</CardGroup>
102 changes: 102 additions & 0 deletions en/sandbox/cash-out.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: 'Cash-out (SPEI send)'
description: 'How to send SPEI to a destination CLABE in sandbox.'
mode: 'wide'
---

## What it does

`POST /api/spei/cash-out` requests a SPEI transfer to a destination CLABE. In sandbox, the full accounting pipeline runs — balance is debited, fee is charged, statement entry is generated — but the Banxico call is simulated.

The HTTP response is always `201 Created` with `status: PENDING`. The final outcome arrives via `cash_out` webhook ~1 second later (`success` scenario) or as forced by the scenario.

## Prerequisite

Your sandbox account needs balance. Run at least one [cash-in](/en/sandbox/cash-in) first — simulated balance is debited just like in production.

## Example

```bash
curl -X POST https://sandbox.ntxpay.com/api/spei/cash-out \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amountCentavos": 15000,
"destinationClabe": "012180001234567890",
"beneficiaryName": "Maria Lopez",
"beneficiaryTaxId": "LOPM850101ABC",
"externalId": "payout-001",
"description": "Supplier payment"
}'
```

### Response (201)

```json
{
"id": 12346,
"externalId": "payout-001",
"status": "PENDING",
"amountCentavos": 15000,
"clabe": "012180001234567890"
}
```

## Expected webhook

After ~1 second (`success` scenario):

```json
{
"event": "cash_out",
"deliveryId": "...",
"transaction": {
"id": 12346,
"externalId": "payout-001",
"status": "CONFIRMED",
"amountCentavos": 15000,
"clabe": "012180001234567890",
"referenceNumerical": "9876543",
"confirmedAt": "2026-03-26T10:00:01.000Z"
},
"errorCode": null,
"errorMessage": null
}
```

## Useful error scenarios

Force specific behaviors via the `X-Sandbox-Scenario` header:

```bash
curl -X POST https://sandbox.ntxpay.com/api/spei/cash-out \
-H "Authorization: Bearer $TOKEN" \
-H "X-Sandbox-Scenario: error:insufficient-funds" \
-H "Content-Type: application/json" \
-d '{ ... }'
```

| Scenario | When to use |
|---|---|
| `error:insufficient-funds` | Validate UX when your customer tries to pay without balance |
| `error:invalid-clabe` | Validate CLABE parsing/validation in your frontend |
| `error:account-not-found` | Validate post-network error (CLABE exists locally but not on the SPEI network) |
| `error:bank-rejected` | Validate generic fallback |
| `delayed:30s` | Validate "transfer in progress" UX |

See [Scenarios](/en/sandbox/scenarios) for the full list.

## Synchronous validations

Even in sandbox, some validations happen **before** the `201` is returned:

| Synchronous error | HTTP | When |
|---|---|---|
| `400 INVALID_AMOUNT` | `400` | `amountCentavos <= 0` |
| `400 INVALID_CLABE_FORMAT` | `400` | CLABE with invalid format (non-numeric, wrong length) |
| `400 DUPLICATE_EXTERNAL_ID` | `400` | `externalId` already used in another transaction for this account |
| `400 INSUFFICIENT_FUNDS` | `400` | Real balance below `amountCentavos + fee` (without using a scenario) |

<Info>
Scenarios prefixed with `error:` affect the **webhook** — the synchronous response is always `201 PENDING`. Structural validations like format/duplication fail synchronously.
</Info>
68 changes: 68 additions & 0 deletions en/sandbox/introduction.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: 'NTX Pay Sandbox'
description: 'Test environment with high fidelity to the NTX Pay México production pipeline.'
mode: 'wide'
---

## What it is

The NTX Pay sandbox lets your integration exercise **cash-in**, **cash-out**, **refund**, and **webhooks** without moving real money. Unlike simplistic mocks, the full accounting pipeline (TigerBeetle balance, limit validation, fee charging, statement generation, webhook delivery via outbox) is exercised intact. Only the external provider (SPEI/Banxico) is simulated.

<Info>
Every NTX Pay integration starts in sandbox. The endpoints, payloads, and webhooks described in this documentation are the final ones — when production is enabled for your company, the same code works by just swapping credentials.
</Info>

## How to enable

Your API credentials are **structurally the same** as those you would use in production. The difference lives at the account level: accounts with `mainProvider: "sandbox"` route every SPEI call internally to the NTX simulator. To create a sandbox account, contact your Account Manager or write to `contact@ntxpay.com` — onboarding is instant and KYC is auto-approved.

## Base URL

| Environment | URL |
|---|---|
| Sandbox | `https://sandbox.ntxpay.com` |

All documented routes (`/api/auth/token`, `/api/spei/cash-in`, `/api/spei/cash-out`, `/api/oxxo/cash-in`, `/api/transactions`, `/api/webhooks-config`) are available exactly at this host.

## Test scenarios

You control the behavior of each call via the `X-Sandbox-Scenario` HTTP header. Without the header, the sandbox returns **success** by default. See [Scenarios](/en/sandbox/scenarios) for the full list of supported error, success, and delay scenarios.

## Webhooks

Register your `webhookUrl` on the sandbox account exactly as you would in production — via `POST /api/webhooks-config`. Events are delivered by the same outbox engine we use in prod, with the same signatures, headers (`X-NTXPay-Delivery`), and retry policy.

## Differences vs Production

| Aspect | Sandbox | Production |
|---|---|---|
| Base URL | `https://sandbox.ntxpay.com` | `https://api.ntxpay.com` |
| Provider | `sandbox` (simulated) | Real bank (Banxico/SPEI) |
| Balance | Simulated | Real funds |
| SPEI cash-in confirmation | Immediate (~1s) | Real (seconds to minutes) |
| `X-Sandbox-Scenario` | Supported | Rejected with `400` |
| Cost | Free | Per contract |

## Next steps

<CardGroup cols={2}>
<Card title="Authentication" href="/en/sandbox/authentication">
How to obtain the JWT in sandbox using your credentials.
</Card>
<Card title="Scenarios" href="/en/sandbox/scenarios">
Full list of scenarios available via `X-Sandbox-Scenario`.
</Card>
<Card title="Cash-in" href="/en/sandbox/cash-in">
Receive via SPEI in sandbox.
</Card>
<Card title="Cash-out" href="/en/sandbox/cash-out">
Send via SPEI in sandbox.
</Card>
<Card title="Webhooks" href="/en/sandbox/webhooks">
How the sandbox delivers webhooks and how to test dedupe.
</Card>
</CardGroup>

## Support

`support@ntxpay.com`
Loading
Loading