# Testing with the Sandbox

Testing a boleto integration against a real bank is slow and partly impossible. You need a business account, a real payer willing to pay a real charge, and for anything involving a due date you would have to wait for the date to arrive.

The Sandbox connector removes all of that. It issues boletos that look real and moves them through the status you choose by themselves, firing the same webhooks a real bank would.

<Callout variant="warning" title="Shaped, not spendable">
Sandbox boletos carry a correctly-shaped digitable line, barcode and PIX payload — the right lengths and field structure, so your parsing and rendering are genuinely exercised. They are **not** valid payment instruments: the check digits are deliberately not computed. Nothing issued here can be paid at a real bank.
</Callout>

## What it lets you test

```mermaid
graph TD
  A[Create a boleto] --> B{The cents of the amount<br/>pick the scenario}
  B --> C[PAID<br/>full or partial]
  B --> D[OVERDUE<br/>no waiting for the date]
  B --> E[CANCELLED]
  D --> F[PROTESTED]
  D --> C
  C --> G[Your webhook handler runs<br/>against every path]
  E --> G
  F --> G
```

You do not send any request to move a boleto. The Sandbox performs each step by itself, **5 minutes** apart, and fires a `boleto/updated` webhook every time, exactly as a bank's settlement notification would.

The transitions the real API refuses are refused here too. A `PAID` boleto cannot be cancelled, and nothing returns to `OPEN` — so a handler that assumes a status can only move forwards is validated rather than accidentally passing.

## Choosing a scenario

The scenario is set by the **cents of the boleto `amount`**:

| Amount ends in | What happens |
|---|---|
| `,00` (e.g. `100.00`) | After 5 minutes → `PAID` for the full amount |
| `,01` | After 5 minutes → `PAID` for half the amount (partial payment: `amountPaid` is lower than `amount`) |
| `,02` | After 5 minutes → `OVERDUE`; 5 minutes later → `PAID` (paid after the due date) |
| `,03` | After 5 minutes → `OVERDUE`; 5 minutes later → `PROTESTED` |
| `,04` | After 5 minutes → `CANCELLED` |
| `,05` | After 5 minutes → `OVERDUE`, and it stays there |
| Any other cents | Stays `OPEN` — use it to test your own cancellation with [POST /boletos/{id}/cancel](/reference/boleto-cancel) |

Cancelling a boleto before a scheduled step runs stops its scenario.

## Getting started

<Callout variant="info" title="Prefer to click through it first?">
The [Pluggy Dashboard](https://dashboard.pluggy.ai) does all of this without code, under **Payments → Boletos** (Beta): create a Sandbox connection, issue a boleto with the cents of the scenario you want, and watch its status change. The requests below are the same ones it sends.
</Callout>

<StepList>
<Step title="Create a Sandbox Boleto Connection">
Use connector `8` (Pluggy Bank Business) and [POST /boleto-connections](/reference/boleto-connection-create). No real credentials are involved:

```json
{
  "connectorId": 8,
  "credentials": {}
}
```
</Step>

<Step title="Issue a boleto with the amount for your scenario">
Identical to any other institution — same request, same response shape. Amounts are in reais, and the cents pick the scenario. This one ends in `,01`, so it will be paid short:

```json
{
  "boletoConnectionId": "{YOUR-SANDBOX-CONNECTION-ID}",
  "boleto": {
    "seuNumero": "TEST-001",
    "amount": 123.01,
    "dueDate": "2026-03-01",
    "payer": {
      "taxNumber": "12345678000199",
      "name": "Example SA",
      "addressState": "SP",
      "addressZipCode": "01239030",
      "addressCity": "São Paulo",
      "addressStreet": "Rua Example"
    }
  }
}
```
</Step>

<Step title="Wait for the webhooks">
Five minutes later the Sandbox settles the boleto and a `boleto/updated` webhook reaches your endpoint, the same one a real bank's notification would produce. Scenarios with two steps send a second webhook five minutes after the first.
</Step>

<Step title="Check your handler ran">
[GET /boletos/{id}](/reference/boleto-get) returns the boleto in its new state, with `amountPaid`, `paidAt` and `paymentOrigin` populated for paid boletos — the same fields, in the same places, as a boleto settled at a real bank.
</Step>
</StepList>

## A test worth writing

The failure mode that costs the most is a webhook handler that reconciles against the wrong field or assumes payment is always in full. The Sandbox makes both cheap to cover:

| Case | How to produce it |
|---|---|
| Paid in full | Issue with an amount ending in `,00` |
| Paid short | Issue with an amount ending in `,01` |
| Paid after the due date | Issue with an amount ending in `,02` |
| Protested | Issue with an amount ending in `,03` |
| Cancelled by the bank | Issue with an amount ending in `,04` |
| Overdue and unpaid | Issue with an amount ending in `,05` |
| Cancelled by you before payment | Issue with any other cents, then call [POST /boletos/{id}/cancel](/reference/boleto-cancel) |
| Duplicate webhook | Make your endpoint idempotent and deliver the same event to it twice yourself |

<Callout variant="info" title="Move on to a real bank">
When your handler survives all of these, point the same code at an [Inter connection](/docs/boleto/inter). Nothing but the connection id changes.
</Callout>