SDK de PHP
Un archivo, sin dependencias. createInvoice(), getInvoice() y verifyWebhook(): se copia en cualquier proyecto.
Un único SDK de PHP sin dependencias: crea facturas, redirige a la página de pago y verifica los webhooks firmados. Para cualquier otro lenguaje está REST.
api.payora.moneyUna llamada a la API con un ID de pedido y un importe en fíat o cripto devuelve una URL de pago alojada.
Elige una moneda, escanea el QR o abre su cartera, y el checkout confirma en vivo al finalizar.
Verifica la firma HMAC, acredita el pedido una sola vez y mira cómo el pago llega a tu saldo.
Un archivo, sin dependencias. createInvoice(), getInvoice() y verifyWebhook(): se copia en cualquier proyecto.
Las peticiones y los webhooks van firmados con HMAC-SHA256, con margen de tiempo y claves de idempotencia.
JSON sencillo sobre HTTPS: intégralo desde cualquier tecnología, no solo desde PHP.
<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));
// 1 — create an invoice (fiat-priced; the payer picks a coin)
$inv = $payora->createInvoice([
'order_id' => 'ORDER-42',
'mode' => 'fiat',
'amount' => '9.99',
'return_url' => 'https://shop.example/thanks',
'lang' => 'en',
], 'ORDER-42'); // Idempotency-Key: a retry never creates a second invoice
header('Location: ' . $inv['pay_url']);
// 2 — webhook endpoint: ALWAYS verify the signature first
$event = $payora->verifyWebhook(file_get_contents('php://input'), getallheaders());
if ($event === null) { http_response_code(400); exit; }
if ($event['status'] === 'paid') {
creditOrderOnce($event['id'], $event['order_id'], $event['amount'], $event['currency']);
}
http_response_code(200); // 2xx stops retriesconst Payora = require('./payora');
const payora = new Payora('https://api.payora.money', process.env.PAYORA_KEY, process.env.PAYORA_SECRET);
// 1 — create an invoice and redirect
app.post('/checkout', async (req, res) => {
const inv = await payora.createInvoice({ order_id: 'ORDER-42', mode: 'fiat', amount: '9.99' }, 'ORDER-42');
res.redirect(inv.pay_url);
});
// 2 — webhook: verify over the RAW body
app.post('/payora/webhook', express.raw({ type: '*/*' }), (req, res) => {
const event = payora.verifyWebhook(req.body, req.headers);
if (event === null) return res.status(400).end();
if (event.status === 'paid') creditOrderOnce(event.id, event.order_id);
res.status(200).end();
});import os
from payora import Payora
payora = Payora('https://api.payora.money', os.environ['PAYORA_KEY'], os.environ['PAYORA_SECRET'])
# 1 — create an invoice and redirect
inv = payora.create_invoice({'order_id': 'ORDER-42', 'mode': 'fiat', 'amount': '9.99'}, 'ORDER-42')
return redirect(inv['pay_url'])
# 2 — webhook: verify over the RAW body
event = payora.verify_webhook(request.get_data(), dict(request.headers))
if event is None:
return ('', 400)
if event['status'] == 'paid':
credit_order_once(event['id'], event['order_id'])
return ('', 200)# signature = lowercase hex HMAC-SHA256(api_secret, timestamp + "." + raw_body)
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl -X POST https://api.payora.money/v1/invoice \
-H "X-Payora-Key: $PAYORA_KEY" -H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG" -H 'Idempotency-Key: ORDER-42' \
-H 'Content-Type: application/json' -d "$BODY"
# → 201 { "invoice_id": …, "status": "pending", "pay_url": "https://pay.payora.money/i/…", "expires": … }Consigue tu API key y secret creando una cuenta. El SDK completo está en tu panel.
Cada llamada, salvo /v1/health, lleva tres cabeceras firmadas. La firma demuestra que la solicitud viene de quien tiene tu api_secret y que no se modificó por el camino.
| Cabecera | Valor | Descripción |
|---|---|---|
X-Payora-Key | pk_… | Tu api_key pública. Identifica la tienda. |
X-Payora-Timestamp | unix seconds | El momento en que firmaste la solicitud, en segundos Unix. |
X-Payora-Signature | hex · 64 | HMAC-SHA256 de timestamp + "." + cuerpo sin procesar con tu api_secret como clave, en hex en minúsculas. |
X-Payora-Test | 1 | Marca la factura como una prueba de tu integración: es real y puede recibir monedas, pero no entra en los informes de demanda y conversión. |
Idempotency-Key | string | Opcional en POST /v1/invoice y POST /v1/payout. Un reintento con la misma clave devuelve el primer resultado en lugar de crear un duplicado; la misma clave con otro cuerpo responde 409. |
Content-Type | application/json | Obligatorio en las solicitudes con cuerpo. |
hex( HMAC-SHA256( api_secret, timestamp + "." + raw_body ) )Firma exactamente los bytes que envías. Volver a codificar el JSON después de firmar —aunque solo cambie el orden de las claves o sobre un espacio— rompe la firma.
En una solicitud GET el cuerpo está vacío, así que la cadena firmada es el timestamp seguido de un punto.
La API rechaza un timestamp que se aleje más de 120 segundos de su reloj. Mantén tu servidor sincronizado con NTP.
Guarda api_secret en tu servidor. Firma tanto las solicitudes como los webhooks: quien lo tenga puede crear facturas en tu nombre y falsificar notificaciones.
<?php
$body = json_encode(['order_id' => 'ORDER-42', 'mode' => 'fiat', 'amount' => '9.99'], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $apiSecret); // lowercase hex
$ch = curl_init('https://api.payora.money/v1/invoice');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Payora-Key: ' . $apiKey,
'X-Payora-Timestamp: ' . $ts,
'X-Payora-Signature: ' . $sig,
'Idempotency-Key: ORDER-42',
'Content-Type: application/json',
],
]);
$invoice = json_decode(curl_exec($ch), true);const crypto = require('crypto');
const body = JSON.stringify({ order_id: 'ORDER-42', mode: 'fiat', amount: '9.99' });
const ts = String(Math.floor(Date.now() / 1000));
const sig = crypto.createHmac('sha256', API_SECRET).update(ts + '.' + body).digest('hex');
const res = await fetch('https://api.payora.money/v1/invoice', {
method: 'POST',
headers: {
'X-Payora-Key': API_KEY,
'X-Payora-Timestamp': ts,
'X-Payora-Signature': sig,
'Idempotency-Key': 'ORDER-42',
'Content-Type': 'application/json',
},
body, // send exactly the bytes you signed
});import hashlib, hmac, json, time, urllib.request
body = json.dumps({'order_id': 'ORDER-42', 'mode': 'fiat', 'amount': '9.99'}, separators=(',', ':'))
ts = str(int(time.time()))
sig = hmac.new(API_SECRET.encode(), (ts + '.' + body).encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request('https://api.payora.money/v1/invoice', data=body.encode(), method='POST', headers={
'X-Payora-Key': API_KEY,
'X-Payora-Timestamp': ts,
'X-Payora-Signature': sig,
'Idempotency-Key': 'ORDER-42',
'Content-Type': 'application/json',
})
invoice = json.load(urllib.request.urlopen(req))# GET has an empty body: the signed string is just the timestamp and a dot
TS=$(date +%s)
SIG=$(printf '%s.' "$TS" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl https://api.payora.money/v1/invoice/1042 \
-H "X-Payora-Key: $PAYORA_KEY" \
-H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG"Todo opcional excepto order_id y amount. El modo fiat permite al pagador elegir cualquier moneda habilitada en tu cuenta; el modo crypto también necesita currency.
| Parámetro | Tipo | Descripción |
|---|---|---|
order_id | string · required | Tu referencia de pedido única (≤190 caracteres). Una repetición se rechaza con 409: a salvo de envíos duplicados. |
amount | string · required | Importe en fiat (mode=fiat) o importe cripto exacto (mode=crypto). |
mode | fiat | crypto | Por defecto fiat: la tasa se fija por moneda en el checkout. crypto fija la moneda y el importe de antemano. |
fiat_currency | string | Código ISO de fiat para el modo fiat (default USD). |
currency | string | Moneda para el modo crypto (p. ej., TON, TRX, USDT_TRON). |
return_url | https URL | A dónde devuelve al cliente el botón «Volver a la tienda» del checkout: a tu sitio. Solo http(s) absoluto. |
lang | en·ru·uk·es·de | Idioma del checkout, para que un cliente de tu tienda rusa vea el checkout en ruso, no en inglés. También configurable como ?lang=ru en la URL de pago. |
ttl | seconds | Cuánto tiempo se puede pagar la factura (120–86400 segundos, 1800 por defecto). |
customer_email | Opcional: aparece en el recibo y se envía por correo al pagador. | |
notes | string | Nota de formato libre almacenada en la factura. |
return_url y lang también funcionan como parámetros de consulta añadidos a la pay_url que devuelve la API — <pay_url>&lang=ru&return_url=https://shop.example/thanks. Envía siempre al comprador a la pay_url devuelta por la API: lleva un token de acceso único por factura, por lo que no se puede acceder a una página de checkout adivinando números de factura. No reconstruyas el enlace a partir del id de la factura.
Rellena los campos y copia un comando cURL listo. Se firma solo en tu terminal con $PAYORA_KEY y $PAYORA_SECRET: esta página nunca ve tus claves y no envía nada.
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99","fiat_currency":"USD","return_url":"https://shop.example/thanks","lang":"es","ttl":1800}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl -X POST https://api.payora.money/v1/invoice \
-H "X-Payora-Key: $PAYORA_KEY" \
-H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG" \
-H 'Idempotency-Key: ORDER-42' \
-H 'Content-Type: application/json' \
-d "$BODY"Desde esta página no se envía nada.
Cuando una factura o un pago cambia de estado, Payora envía por POST un evento JSON firmado a tu URL de callback. Verifica primero la firma, abona el pedido una sola vez y responde 2xx.
| Evento | status | Cuándo se envía |
|---|---|---|
invoice.paid | paid | El pago está confirmado en la cadena. Incluye el importe, la moneda y tx_hash. |
invoice.underpaid | underpaid | La factura venció tras recibir menos del importe esperado. |
invoice.expired | expired | La factura venció sin recibir nada. |
invoice.cancelled | cancelled | La factura se canceló. Si ya había llegado dinero, el evento lo describe. |
invoice.cancel_settled | — | El dinero que llegó a una factura cancelada ya está resuelto: abonado o devuelto. |
invoice.late_payment | — | Llegó dinero a una factura después de cancelarla. Se envía una vez por cada ingreso así; result indica si se abonó o se está devolviendo. |
payout.sent | sent | Un lote de pagos se firmó y se difundió. Incluye cada elemento con su tx_hash. |
| Cabecera | Valor | Descripción |
|---|---|---|
X-Payora-Id | uuid | Id único del evento, igual que id en el cuerpo. Guárdalo para ignorar repeticiones. |
X-Payora-Event | invoice.paid | Nombre del evento. |
X-Payora-Timestamp | unix seconds | Cuándo se firmó este intento. |
X-Payora-Signature | hex · 64 | El mismo esquema que en las solicitudes: HMAC-SHA256 de timestamp + "." + cuerpo sin procesar con tu api_secret. |
| Campo | Tipo | Descripción |
|---|---|---|
id | uuid | Id del evento para evitar duplicados. |
event | string | Nombre del evento. |
invoice_id | int | Número de factura en Payora. |
order_id | string | Tu referencia de pedido, tal como la enviaste al crear la factura. |
status | string | paid en invoice.paid. |
currency | string | Moneda con la que se pagó, p. ej. USDT_TRON. |
amount | decimal string | Importe recibido. |
amount_units | integer string | El mismo importe en las unidades mínimas de la moneda: úsalo para comparaciones exactas. |
expected | decimal string | Importe que pedía la factura. |
fiat_amount | decimal string | null | Precio en fiat de la factura tal como la creaste; null en facturas en modo cripto. |
fiat_currency | string | null | Código ISO de fiat_amount; null en facturas en modo cripto. |
tx_hash | string | Hash de la transacción en la cadena. |
paid_at | unix seconds | Cuándo se creó el evento. |
test | bool | true en facturas de prueba. |
{
"id": "6f1c2a4e-8b0d-4c1e-9a57-2f3d9e1b7c40",
"event": "invoice.paid",
"invoice_id": 1042,
"order_id": "ORDER-42",
"status": "paid",
"currency": "USDT_TRON",
"amount": "9.99",
"amount_units": "9990000",
"expected": "9.99",
"fiat_amount": "9.99",
"fiat_currency": "USD",
"tx_hash": "3f9a…c21e",
"paid_at": 1758038400,
"test": false
}Si el importe recibido difiere de la factura, se añaden amount_received y amount_expected.
Cualquier respuesta distinta de 2xx, o ninguna en 15 segundos, se reintenta con espera exponencial de como máximo una hora entre intentos, hasta 12 intentos. Cada intento se firma de nuevo con otro timestamp; el cuerpo y el id del evento no cambian, así que deduplica por id.
Tras el último intento el evento se marca como fallido y el propietario de la tienda recibe un correo. Nunca se siguen redirecciones y la URL de callback debe ser accesible desde internet: nada de localhost ni direcciones privadas.
hash_equals, crypto.timingSafeEqual, hmac.compare_digest.<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));
$raw = file_get_contents('php://input'); // raw, before any parsing
$event = $payora->verifyWebhook($raw, getallheaders()); // HMAC + ±300 s window, constant-time
if ($event === null) { http_response_code(400); exit; } // bad / forged / stale
if ($event['event'] === 'invoice.paid' && !alreadyProcessed($event['id'])) {
creditOrder($event['order_id'], $event['amount'], $event['currency'], $event['tx_hash']);
rememberProcessed($event['id']);
}
http_response_code(200); // 2xx stops retries// Express: capture the RAW body for this route
app.post('/payora/webhook', express.raw({ type: '*/*' }), (req, res) => {
const event = payora.verifyWebhook(req.body, req.headers); // HMAC + ±300 s, timingSafeEqual
if (event === null) return res.status(400).end();
if (event.event === 'invoice.paid' && !alreadyProcessed(event.id)) {
creditOrder(event.order_id, event.amount, event.currency, event.tx_hash);
rememberProcessed(event.id);
}
res.status(200).end(); // 2xx stops retries
});# Flask: request.get_data() is the exact raw body
@app.post('/payora/webhook')
def payora_webhook():
event = payora.verify_webhook(request.get_data(), dict(request.headers))
if event is None:
return ('', 400) # bad / forged / stale
if event['event'] == 'invoice.paid' and not already_processed(event['id']):
credit_order(event['order_id'], event['amount'], event['currency'], event['tx_hash'])
remember_processed(event['id'])
return ('', 200) # 2xx stops retries# Recompute the signature of a delivery you saved byte-for-byte to body.json
TS='1758038400' # X-Payora-Timestamp
SIG='…' # X-Payora-Signature
EXPECTED=$(printf '%s.' "$TS" | cat - body.json | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
[ "$EXPECTED" = "$SIG" ] && echo valid || echo forgedIntroduce un secreto, un timestamp y el cuerpo sin procesar para ver la firma exacta que enviaría Payora, o comprueba una que hayas recibido.
Funciona en tu navegador con WebCrypto. Nada de lo que escribas sale de esta página. En un ordenador compartido usa un secreto de prueba, no el real.
1758038400.{…}X-Payora-Timestamp: 1758038400
X-Payora-Signature: …Pega una firma para comparar
Envía cripto a hasta 500 carteras en una sola petición firmada e idempotente: nóminas, pagos a afiliados o retiradas. El total del lote más la comisión por línea se retiene al instante en tu saldo interno; después el Operador firma y difunde, y se dispara un webhook firmado payout.sent. La misma autenticación HMAC que todo lo demás.
# create a payout batch — funds held instantly, sent by the operator
curl -X POST https://api.payora.money/v1/payout \
-H 'X-Payora-Key: pk_…' -H 'X-Payora-Timestamp: 1700000000' \
-H 'X-Payora-Signature: <hmac-sha256>' -H 'Idempotency-Key: payroll-2026-01' \
-d '{"currency":"USDT_TRON","items":[
{"address":"TR7…","amount":"250.00"},
{"address":"TX9…","amount":"99.50","tag":"batch-A"}]}'
# → 201 Created
{ "batch_id": 812, "status": "pending", "currency": "USDT_TRON",
"item_count": 2, "total": "349.50", "fee": "…", "items": [ … ] }
# poll status, or receive a signed payout.sent webhook when it is sent
curl https://api.payora.money/v1/payout/812 -H 'X-Payora-Key: …' …<?php
// The one-file PHP SDK covers invoices and webhooks; payouts are one signed POST.
$body = json_encode(['currency' => 'USDT_TRON', 'items' => [
['address' => 'TR7…', 'amount' => '250.00'],
['address' => 'TX9…', 'amount' => '99.50', 'tag' => 'batch-A'],
]], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $apiSecret);
$ch = curl_init('https://api.payora.money/v1/payout');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Payora-Key: ' . $apiKey, 'X-Payora-Timestamp: ' . $ts, 'X-Payora-Signature: ' . $sig,
'Idempotency-Key: payroll-2026-01', 'Content-Type: application/json']]);
$batch = json_decode(curl_exec($ch), true); // 201: batch_id, status, totals, fee, itemsconst batch = await payora.createPayout({
currency: 'USDT_TRON',
items: [
{ address: 'TR7…', amount: '250.00' },
{ address: 'TX9…', amount: '99.50', tag: 'batch-A' },
],
}, 'payroll-2026-01'); // Idempotency-Key: a retried run never doubles
const status = await payora.getPayout(batch.batch_id);
// still pending? await payora.cancelPayout(batch.batch_id) releases the holdbatch = payora.create_payout({
'currency': 'USDT_TRON',
'items': [
{'address': 'TR7…', 'amount': '250.00'},
{'address': 'TX9…', 'amount': '99.50', 'tag': 'batch-A'},
],
}, 'payroll-2026-01') # Idempotency-Key: a retried run never doubles
status = payora.get_payout(batch['batch_id'])
# still pending? payora.cancel_payout(batch['batch_id']) releases the holdLa validación es estricta: cualquier dirección incorrecta o importe con precisión excesiva rechaza el lote completo (HTTP 422) con los índices de los elementos problemáticos: una nómina nunca descarta a un destinatario en silencio. Cancela un lote aún pendiente con POST /v1/payout/{id}/cancel para liberar la retención. Consulta el análisis de la arquitectura.
Cada endpoint habla JSON simple sobre HTTPS en api.payora.money. Todos excepto /v1/health usan la misma autenticación de solicitudes con HMAC-SHA256. La especificación legible por máquina está en openapi.json.
| Endpoint | Acceso | Descripción |
|---|---|---|
GET/v1/health | no auth | Estado del servicio más la lista de monedas habilitadas. |
POST/v1/invoice | auth | Crea una factura (modo fiat o crypto). Admite Idempotency-Key. Devuelve 201 con invoice_id, status, pay_url y expires; el modo crypto añade la dirección, el importe y el deeplink. |
GET/v1/invoice/{id} | auth | Estado de la factura (solo facturas propias): status, remaining y charge — currency, dirección, importes esperados/recibidos y tx_hash. |
POST/v1/payout | auth · Pro+ | Crea un lote de pagos masivos (validación estricta, fondos retenidos al instante). Admite Idempotency-Key. Devuelve 201 con la vista del lote: batch_id, status, totales, comisión, elementos. |
GET/v1/payout | auth | Lista tus lotes de pagos recientes (?limit=, default 50). |
GET/v1/payout/{id} | auth | Estado y elementos del lote de pagos (solo lotes propios). |
POST/v1/payout/{id}/cancel | auth | Cancela un lote aún pendiente y libera el saldo retenido. Devuelve 409 si el lote ya no se puede cancelar. |
Ningún endpoint coincide.
Archivos únicos sin dependencias. Todos los SDK firman solicitudes y verifican webhooks del mismo modo.
PHP 7.4+ con cURL, un solo archivo. createInvoice, getInvoice y verifyWebhook.
Node.js solo con https y crypto integrados. Facturas, saldo, pagos y verifyWebhook.
Solo la biblioteca estándar de Python. Facturas, saldo, pagos y verify_webhook.
Una colección de Postman que firma cada llamada por ti.
La especificación legible por máquina de cada endpoint, para generadores de código y clientes de API.
Un módulo listo hace todo lo anterior por ti: factura, redirección y webhook verificado.
Respuestas directas, también a las incómodas: custodia, comisiones y qué pasa cuando algo sale mal.
Centro de ayudaInstala el SDK de PHP de un solo archivo (sin dependencias) o llama directamente a la API REST. Crea una factura con createInvoice(), redirige al cliente a su pay_url y gestiona un único evento de webhook: status=paid. Una persona con experiencia lo tiene en producción en 30–60 minutos.
Sí. Todo es JSON sencillo sobre HTTPS, así que puedes integrarlo desde cualquier lenguaje. El SDK de PHP es solo una envoltura cómoda sobre los mismos endpoints: crear factura, consultar factura y verificar firmas de webhook.
Sí. Pasa return_url al crear la factura (o ?return_url= en el enlace de pago) y la página de pago mostrará un botón «Volver a la tienda» tanto en la pantalla de éxito como durante el pago. Pasa lang (en, ru, uk, es, de), o ?lang= en el enlace, para que quien llegue desde tu tienda en español vea la página de pago en español y no en inglés. Ambos son opcionales; return_url debe ser una URL http(s) absoluta.
Las peticiones y los webhooks se autentican con HMAC-SHA256 sobre la marca de tiempo y el cuerpo en bruto usando tu api_secret, con una ventana de ±120 segundos y claves de idempotencia. Las firmas se comparan en tiempo constante para evitar ataques por tiempo.
Crea una cuenta gratuita. Tu api_key pública y tu api_secret secreta aparecen en el panel, junto con el SDK descargable y la referencia completa de webhooks. Puedes rotar las claves cuando quieras desde los ajustes.
Un evento JSON firmado con el identificador de la factura, tu order_id, el estado (por ejemplo paid), el importe y la moneda, y un identificador de evento único para la idempotencia. Verifica siempre la firma antes de dar por pagado un pedido y guarda el identificador del evento para no procesarlo dos veces en los reintentos.
Sí. POST /v1/payout envía cripto a hasta 500 carteras en una sola petición: un {address, amount, optional tag} por línea, en cualquier moneda habilitada en tu cuenta. Usa la misma autenticación HMAC-SHA256 y la misma Idempotency-Key que la API de facturas, así que reintentar nunca crea un lote duplicado. El total del lote más la comisión por línea se reserva en tu saldo de inmediato; el Operador firma y difunde, y entonces se dispara un webhook firmado payout.sent. La validación es estricta: cualquier dirección no válida o importe con demasiados decimales rechaza el lote entero (422), para que una nómina nunca pierda un destinatario en silencio.
Arma la lista de destinatarios en tu propio sistema y llama a POST /v1/payout una vez por ejecución con una Idempotency-Key estable (por ejemplo, payroll-2026-01). Consulta GET /v1/payout/{id} o espera el webhook payout.sent para confirmar la entrega y registrar cada hash. Enviar el lote reserva los fondos pero no los mueve por sí solo: el Operador firma y difunde, de modo que nada sale con una simple llamada a la API.
Un único SDK de PHP sin dependencias: crea facturas, redirige a la página de pago y verifica los webhooks firmados. Para cualquier otro lenguaje está REST.