BRE-B dispersions
Resolve BRE-B keys and disperse to them with the same WompiPayoutsClient — key preview, automatic /v2 routing, mixed batches, webhooks and sandbox keys.
BRE-B is Colombia’s instant-transfer rail: beneficiaries register a key (email, phone, alphanumeric alias, legal id, or establishment code) with their bank, and you pay them with just that key — no bank, account type, or account number needed.
BRE-B lives on the same WompiPayoutsClient as bank
dispersions, with the same credential pair from Desarrollo → Programadores →
Pagos a Terceros in the Wompi dashboard. BRE-B creation, key resolution and
lifecycle reads sit on /v2, while bank endpoints sit on /v1. Creation and
key resolution route automatically; batch reads default to /v1, so pass
apiVersion: "v2" when reading BRE-B or mixed batches:
import { WompiPayoutsClient } from "@pulgueta/wompi";
const payouts = new WompiPayoutsClient({
apiKey: process.env.WOMPI_PAYOUTS_API_KEY!,
userPrincipalId: process.env.WOMPI_PAYOUTS_USER_PRINCIPAL_ID!,
sandbox: true, // uses api.sandbox.payouts.wompi.co
});
resolveBrebKey(keyValue, keyType?)
Resolves a BRE-B key (GET /v2/breb/keys/resolve/{keyValue}) to its masked
holder before you pay. Read-only: nothing moves, and no idempotency key is
involved. Show the result to your user and ask them to confirm the beneficiary
— this is Wompi’s recommended first step and it materially reduces failed
payouts. Full holder details are never returned through this endpoint.
const [error, holder] = await payouts.resolveBrebKey(
"@JUANPEREZ",
"ALPHANUMERIC",
);
if (error) throw error;
holder.holderName; // "JUA*** PER*** GAR***"
holder.financialEntity?.name; // "BANCOLOMBIA"
holder.keyType; // "ALPHANUMERIC"
holder.keyValue; // "@JUA***"
keyType is optional — Wompi infers it from the format — but sending it avoids
ambiguity (a 10-digit number could be a phone or a legal id).
keyType |
Format | Example |
|---|---|---|
ALPHANUMERIC |
@ + 5–20 alphanumeric chars |
@JUANPEREZ |
MAIL |
Valid email | juan@email.com |
PHONE |
10 digits, starts with 3 | 3001234567 |
IDENTIFICATION |
1–18 alphanumeric chars | 1234567890 |
ESTABLISHMENT_CODE |
8 digits | 12345678 |
Resolution failures surface as WompiPayoutApiError with the Wompi code and
HTTP statusCode: EXC_033 (invalid format, 400), EXC_034 (key not
registered or not active, 404), EXC_035 (key exists but inactive, 400),
EXC_036 (resolution service unavailable, 503) and EXC_037 (resolution
timeout, 503). Wompi classifies EXC_036/EXC_037 as technical — retry those
with backoff, never the business codes.
Paying a key with createPayout
There is no separate BRE-B create method: createPayout
takes BRE-B transactions in the same transactions array. A BRE-B transaction
sends key instead of the bank trio (bankId + accountType +
accountNumber); the SDK posts the batch to /v2/payouts as soon as any
transaction carries a key, and to /v1/payouts otherwise.
const [error, created] = await payouts.createPayout(
{
reference: "providers-2026-07",
accountId: account.id, // from listAccounts()
paymentType: "PROVIDERS", // PAYROLL | PROVIDERS | OTHER
transactions: [
{
key: "@JUANPEREZ",
name: "Juan Perez",
email: "juan@example.com", // required on BRE-B transactions
amount: 150_000, // cents: $1,500.00 COP
reference: "providers-2026-07-jperez",
},
],
},
{ idempotencyKey: "providers-2026-07" },
);
if (error) throw error;
created.payoutId; // persist this — it identifies the batch
created.success; // transactions accepted for processing
created.failed; // transactions rejected by validation
BRE-B transaction fields
| Field | Required | Notes |
|---|---|---|
key |
Yes | The registered BRE-B key. Excludes the bank destination trio. |
amount |
Yes | Positive integer, in COP cents. |
name |
Yes | Beneficiary name. |
email |
Yes | Required here — unlike bank transactions, where it’s optional. |
legalIdType legalId |
No | Optional, but as a pair: both or neither. CC CE NIT PP TI DNI. |
personType |
No | NATURAL or JURIDICA. |
phone description |
No | |
reference |
No | Max 40 characters; letters, numbers and hyphens only. |
There is no keyType field when creating — that’s a query parameter of
resolveBrebKey only. Wompi resolves the key on its own and rejects the
individual transaction if the key is invalid or inactive.
The SDK validates all of this locally before the request fires: a transaction
mixing key with the bank trio, a missing email, an unpaired
legalIdType/legalId, or an over-long reference resolves to a WompiError
without touching the network.
Mixed batches
Bank and BRE-B transactions can share one batch (it goes to /v2); a single
transaction must pay either a key or a bank account, never both:
const [error, created] = await payouts.createPayout(
{
reference: "mixed-2026-07",
accountId: account.id,
paymentType: "PROVIDERS",
transactions: [
{
key: "@JUANPEREZ",
name: "Juan Perez",
email: "juan@example.com",
amount: 150_000,
},
{
legalIdType: "CC",
legalId: "1234567890",
bankId: bank.id, // from listBanks()
accountType: "AHORROS",
accountNumber: "9876543210",
name: "Carlos Gomez",
email: "carlos@example.com",
amount: 250_000,
},
],
},
{ idempotencyKey: "mixed-2026-07" },
);
Scheduled (dispersionDatetime), recurring (recurring) and the sandbox-only
transactionStatus batch fields work exactly as they do for
bank batches.
Reading the result
BRE-B lifecycle reads use /v2 with the same methods and page shapes:
const [payoutError, payout] = await payouts.getPayout(created.payoutId, {
apiVersion: "v2",
});
if (payoutError) throw payoutError;
payout.status;
// "PENDING" | "TOTAL_PAYMENT" | "PARTIAL_PAYMENT" | "REJECTED"
// with the approval flow enabled: "PENDING_APPROVAL" | "NOT_APPROVED"
// under anti-fraud review: "AFE_ON_HOLD" | "AFE_REJECTED"
const [pageError, page] = await payouts.listPayoutTransactions(
created.payoutId,
{ limit: 50, page: 1 },
{ apiVersion: "v2" },
);
if (pageError) throw pageError;
for (const tx of page.records) {
// A string on the REST API; webhook events send an object instead.
const failure =
typeof tx.failureReason === "string"
? tx.failureReason
: tx.failureReason?.description;
console.log(tx.reference, tx.status, failure ?? "");
}
Continue paginating while page.page < page.pages.
Webhook events
BRE-B results arrive on the same events URL as bank dispersions, with the same
two event names — transaction.updated per dispersal and payout.updated per
batch. Verify them with verifyPayoutEvent from @pulgueta/wompi/server and
the payouts events secret (not the payments one), then narrow with the
payout guards:
import {
isPayoutTransactionUpdatedEvent,
isPayoutUpdatedEvent,
verifyPayoutEvent,
} from "@pulgueta/wompi/server";
export async function POST(request: Request) {
const [error, event] = await verifyPayoutEvent(await request.text(), {
eventsKey: process.env.WOMPI_PAYOUTS_EVENTS_KEY!,
});
if (error) return new Response("Invalid signature", { status: 403 });
if (isPayoutTransactionUpdatedEvent(event)) {
const tx = event.data.transaction;
if (tx.payee.key) {
// BRE-B dispersal — bank events have no payee.key.
const reason =
typeof tx.failureReason === "string"
? tx.failureReason
: tx.failureReason?.description;
console.log("BRE-B", tx.id, "→", tx.status, reason ?? "");
}
}
if (isPayoutUpdatedEvent(event)) {
console.log("batch", event.data.payout.id, "→", event.data.payout.status);
}
return new Response("OK");
}
The two rails share event names but not payload shapes. Branch on the presence
of payee.key:
| Bank dispersal event | BRE-B dispersal event | |
|---|---|---|
payee |
name, document, bank, accountType, accountNumber, email |
key, keyType, name, email, masked legalId, legalIdType, personType, masked accountNumber, keyResolutionId, paymentMethodType — no bank |
failureReason |
Object with code and message |
Object with code and description (e.g. MOL-5021 — amount below minimum, C01 — account inactive) |
Sandbox
Point the client at the sandbox with sandbox: true and the sandbox payouts
credentials. Only the documented test keys resolve — any other key returns
EXC_034:
| Key | Type | Simulates |
|---|---|---|
@elias123 |
ALPHANUMERIC |
Resolves — BANCO DAVIVIENDA |
ecolon@wompi.com |
MAIL |
Resolves — BANCOLOMBIA |
3001234567 |
PHONE |
Resolves — BANCO POPULAR |
1020304050 |
IDENTIFICATION |
Resolves — BANCO POPULAR |
900123456 |
IDENTIFICATION (NIT) |
Resolves — BANCO DAVIVIENDA |
noexiste@test.com |
MAIL |
404 EXC_034 — key not found |
12345 |
— | 400 EXC_033 — invalid key format |
inactiva@test.com |
MAIL |
400 EXC_035 — key inactive |
timeout@test.com |
MAIL |
503 EXC_037 — resolution timeout |
error@test.com |
MAIL |
503 EXC_036 — service unavailable |
transactionStatus: "APPROVED" | "FAILED" forces the outcome of every
transaction in a sandbox batch, exactly as with bank batches, and the
sandbox-only rechargeAccountBalance
tops up the origin account.
See the BRE-B payout example for the full resolve → confirm → pay → webhook flow.