Ticketz PRO incluye un driver de gateway de pago Genérico Externo que permite integrar cualquier proveedor de cobranza sin modificar el código fuente del backend. En lugar de escribir un nuevo driver en TypeScript, implementas un único endpoint HTTP que recibe llamadas RPC de Ticketz y devuelve los datos requeridos.
Referencia canónica: un contrato formal OpenAPI 3.0 está disponible en External-Payment-Driver-OpenAPI.yaml. Úsalo para validar tu implementación con herramientas OpenAPI o para generar stubs de servidor.
1. Cómo funciona
El ExternalPaymentDriver almacena/lee los datos de las facturas en la base de datos de Ticketz y reenvía cada operación de negocio a tu endpoint:
- Crear pago — Ticketz pide a tu endpoint que genere un código PIX, boleto, cobro de tarjeta de crédito o link de checkout externo.
- Crear suscripción — Ticketz pide a tu endpoint que cree una suscripción recurrente y devuelva un
subscriptionId. - Sincronizar pago de suscripción — Ticketz pide a tu endpoint el próximo pago pendiente de la suscripción (p. ej. próximo QR Code PIX, código de barras del boleto o URL de la factura hospedada) para que la factura se actualice antes del vencimiento.
- Obtener estado de la suscripción — Ticketz pide a tu endpoint el estado actual de la suscripción durante el job diario de reconciliación.
- Webhook — Ticketz reenvía el payload del webhook del proveedor a tu endpoint para que lo valides y le indiques a Ticketz si la factura fue pagada o expirada.
- Verificar estado — Ticketz pide a tu endpoint el estado actual de una factura abierta.
- Reportar detalles del driver — Ticketz pide a tu endpoint los campos dinámicos, modos soportados y operaciones soportadas en una única llamada
getDriverDetails. El resultado se cachea y se reutiliza hasta un refresh forzado.
Tu endpoint es responsable de toda la lógica específica del proveedor: autenticación, llamadas a la API, validación de firmas, idempotencia, etc.
El driver externo también soporta las siguientes funciones avanzadas:
- Campos de configuración dinámicos — tu endpoint puede declarar campos de configuración adicionales que aparecen en la UI de Ticketz. Se devuelven dentro de
getDriverDetails. - Archivos de certificado privados — los certificados subidos se almacenan de forma segura en
backend/privatey se envían a tu endpoint codificados en base64 en cada llamada RPC. - Deshabilitar modos — los modos individuales se pueden deshabilitar mediante parámetros del driver (
_externalDisablePix,_externalDisableBoleto,_externalDisableCc,_externalDisableLink). CuandogetDriverDetailsreporta los modos soportados, la lista final es la intersección de los modos del endpoint y los modos habilitados localmente. - Detalles del driver en caché — la respuesta de
getDriverDetailsse cachea en el primer uso y se persiste hasta que un super usuario llama aPOST /payment-gateways/external/refresh-cache.
2. Habilitar el driver
- Ve a Configuración → Gateway de Pago.
- Selecciona Genérico Externo (
external). - Guarda las configuraciones obligatorias:
| Configuración | Valor recomendado |
|---|---|
| URL del Endpoint | https://your-service.example.com/ticketz-payment-gateway (obligatorio) |
| Token de Autenticación | Un secreto fuerte compartido solo con tu servicio (enviado como Authorization: Bearer <token>) |
| Habilitar suscripciones | "true" para crear suscripciones recurrentes vía createSubscription. |
Cuando las suscripciones estén habilitadas, incluye también getSubscriptionStatus en el array operations devuelto por getDriverDetails para que Ticketz pueda reconciliar el estado de la suscripción todos los días.
Tras guardar la URL del Endpoint, el driver carga todos los metadatos de tu endpoint con una única llamada RPC getDriverDetails la primera vez que se necesite. Los botones Cargar campos y Cargar modos ahora leen de ese caché. Para forzar al driver a consultar el endpoint nuevamente (por ejemplo, tras cambiar los campos o modos soportados de tu lado), un super usuario puede llamar a:
POST /payment-gateways/external/refresh-cache
La URL del webhook que tu proveedor debe llamar es:
https://<tu-backend-ticketz>/subscription/webhook
3. Protocolo RPC
Todas las llamadas son peticiones POST a tu endpoint con Content-Type: application/json.
Envelope común de petición
{
"operation": "create | createSubscription | webhook | checkStatus | expire | getDriverDetails | getSubscriptionStatus",
"driver": "external",
"callbackUrl": "https://<tu-backend-ticketz>/subscription/webhook",
"invoiceId": 123,
"txId": "TX-ABC-123",
"paymentMethod": "pix",
"mode": "pix",
"price": 99.90,
"cardData": { ... },
"company": { ... },
"invoice": { ... },
"webhookBody": { ... },
"webhookHeaders": { ... },
"webhookQuery": { ... },
"currentSettings": { ... },
"fileData": { ... },
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}
Los campos se rellenan según la operación:
| Operación | Campos rellenados |
|---|---|
create |
invoiceId, price, paymentMethod, mode, cardData, company, invoice, currentSettings, fileData |
createSubscription |
Igual que create. |
syncSubscriptionPayment |
invoiceId, txId, subscriptionId, paymentMethod, company, invoice, currentSettings, fileData |
webhook |
webhookBody, webhookHeaders, webhookQuery, currentSettings, fileData |
checkStatus |
invoiceId, txId, paymentMethod, invoice, currentSettings, fileData |
expire |
invoiceId, txId, currentSettings, fileData |
getSubscriptionStatus |
subscriptionId, currentSettings, fileData |
getDriverDetails |
currentSettings, fileData |
Envelope común de respuesta
{
"success": true,
"errorCode": "ERR_MY_CUSTOM",
"errorMessage": "Error legible",
"status": "paid | expired | ignored",
"paymentMethod": "pix | boleto | credit_card",
"subscriptionId": "sub-ABC-123",
"txId": "TX-ABC-123",
"value": 99.90,
"qrcode": { "qrcode": "0002012658..." },
"boletoUrl": "https://...",
"boletoTypeable": "00190.00009 ...",
"boletoBarcode": "0019000009...",
"checkoutUrl": "https://checkout.example.com/...",
"expireAt": "2026-06-20T23:59:59Z",
"dueDate": "2026-06-20",
"payGwData": { ... },
"paymentData": { ... },
"fields": [ ... ],
"webhookResponse": { ... }
}
Cuando success es false en una operación create, Ticketz devuelve una respuesta 400 al frontend con:
{
"error": "ERR_MY_CUSTOM",
"message": "Error legible"
}
errorCode y errorMessage son ambos opcionales. Si se proporciona errorMessage, se muestra directamente en el toast del frontend. Si solo se proporciona errorCode, el frontend intenta traducirlo; de lo contrario, muestra el código.
fields se devuelve dentro de getDriverDetails; los nombres de los campos devueltos no deben incluir el prefijo _external — Ticketz lo añade automáticamente.
4. Operaciones en detalle
4.1 create
Ticketz envía esta operación cuando el usuario solicita un nuevo cobro.
Ejemplo de petición:
{
"operation": "create",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"invoiceId": 123,
"price": 99.9,
"paymentMethod": "pix",
"company": {
"id": 5,
"name": "Acme SA",
"email": "financeiro@acme.test",
"dueDate": "2026-06-17",
"recurrence": "MENSAL",
"fiscalData": {
"document": "12.345.678/0001-90",
"name": "Acme SA",
"fiscalEmail": "financeiro@acme.test",
"postalCode": "01001000",
"address": "Rua Exemplo",
"addressNumber": "100",
"province": "Centro",
"city": "São Paulo",
"state": "SP"
},
"externalIds": {}
},
"invoice": {
"id": 123,
"detail": "Assinatura Mensal",
"status": "open",
"value": 99.9,
"currency": "BRL",
"dueDate": "2026-06-17",
"companyId": 5
}
}
Respuesta esperada para PIX:
{
"success": true,
"paymentMethod": "pix",
"txId": "external-tx-123",
"value": 99.9,
"qrcode": { "qrcode": "00020126580014br.gov.bcb.pix..." },
"expireAt": "2026-06-17T23:59:59.000Z",
"payGwData": { "externalId": "external-tx-123", "raw": "..." }
}
Respuesta esperada para boleto:
{
"success": true,
"paymentMethod": "boleto",
"txId": "external-tx-456",
"value": 99.9,
"boletoUrl": "https://banco.example.com/boleto/456",
"boletoTypeable": "00190.00009 01234.567890 12345.678901 2 12345678901234",
"boletoBarcode": "00191234567890123456789012345678901234567890123",
"dueDate": "2026-06-20",
"payGwData": { "externalId": "external-tx-456" }
}
Ticketz almacena txId, paymentMethod, payGwData (como string JSON) y paymentData en la factura. payGwData y paymentData son opcionales; si se omiten, Ticketz construye paymentData a partir de los campos de boleto/PIX. Cuando un cobro expira o se invalida, Ticketz marca la factura como expired y registra expiredAt en paymentData para fines de auditoría.
Semántica de los campos de fecha:
- Usa
expireAtpara cobros de corta duración, como Pix. El frontend renderiza una cuenta regresiva en vivo y puede expirar el cobro automáticamente. - Usa
dueDatepara cobros de largo plazo, como boleto. El frontend lo muestra como una fecha de calendario en lugar de cuenta regresiva. - Cuando se devuelven ambos campos, Ticketz mantiene solo el más relevante:
- cobros
pixmantienenexpireAt. - boletos y demás cobros mantienen
dueDate. - La respuesta de la API y el
paymentDataalmacenado contendrán solo el campo elegido.
- cobros
4.2 webhook
Tu proveedor envía un webhook a Ticketz, y Ticketz lo reenvía a tu endpoint.
Ejemplo de petición:
{
"operation": "webhook",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"webhookBody": {
"event": "payment.received",
"data": {
"txId": "external-tx-123",
"amount": 99.9,
"status": "paid"
}
},
"webhookHeaders": {
"content-type": "application/json",
"x-signature": "sha256=..."
},
"webhookQuery": {}
}
Respuesta esperada:
{
"success": true,
"status": "paid",
"txId": "external-tx-123",
"value": 99.9
}
Valores permitidos para status:
paid—PaymentGatewayServiceslocaliza la factura abierta portxId, verifica que elvaluepagado sea al menos el valor de la factura, marca la factura como pagada y extiende la fecha de vencimiento de la empresa.expired—PaymentGatewayServiceslocaliza la factura abierta portxId, marca la factura comoexpiredy registraexpiredAtenpaymentDatapara fines de auditoría.ignored— Ticketz no hace nada (úsa esto para eventos ping, duplicados, etc.).
Para paid y expired, txId es obligatorio para que Ticketz encuentre la factura internamente, a menos que se proporcione un subscriptionId. Cuando subscriptionId está presente y txId es desconocido, Ticketz resuelve la factura abierta más antigua con ese id de suscripción, actualiza su txId y procesa el pago. El driver en sí no accede a la tabla Invoices; solo reporta txId, subscriptionId y value. El value pagado es opcional; cuando se omite, se asume el valor de la factura. Si el monto pagado es menor que el valor de la factura, el webhook se trata como ignored. Si el monto pagado es mayor que el valor de la factura, la diferencia se almacena en la factura como creditBalance y se aplica como descuento en la próxima factura de suscripción.
Respuesta de webhook personalizada
Algunos proveedores requieren una respuesta HTTP específica cuando entregan un webhook. Por defecto, Ticketz responde al proveedor con { "ok": true }. Puedes sobrescribir esto incluyendo un objeto webhookResponse en tu respuesta de webhook:
{
"success": true,
"status": "paid",
"txId": "external-tx-123",
"value": 99.9,
"webhookResponse": {
"statusCode": 200,
"body": { "received": true },
"headers": {
"content-type": "application/json"
}
}
}
statusCode— código HTTP que Ticketz envía de vuelta. Por defecto200cuando se omite.body— cuerpo JSON devuelto al proveedor.headers— cabeceras de respuesta opcionales (objeto con valores string).
Si se omite webhookResponse, Ticketz mantiene la respuesta por defecto { "ok": true }.
4.3 checkStatus
Ticketz llama a esta operación para facturas abiertas durante la verificación periódica, cuando un admin solicita manualmente un refresh de estado, y cuando el usuario hace clic en Pagar en una factura que ya tiene un cobro abierto.
Ejemplo de petición:
{
"operation": "checkStatus",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"txId": "external-tx-123",
"paymentMethod": "pix"
}
Respuesta esperada:
{
"success": true,
"status": "paid"
}
Los valores permitidos para status son paid, expired e ignored. Si el estado de la respuesta es paid, Ticketz ejecuta el mismo flujo de pago usado por los drivers nativos. Si el estado de la respuesta es expired, Ticketz marca la factura como expired y registra expiredAt en paymentData.
4.4 expire
Reservado para uso futuro. Ticketz puede llamar a esta operación cuando una factura se cancela explícitamente. Tu endpoint no debe depender de ello hoy.
4.5 getDriverDetails
Operación única usada para popular el caché del driver. Ticketz la llama en el primer uso y cachea la respuesta hasta que un super usuario fuerza un refresh. La respuesta debe incluir fields, modes y operations.
El array
fieldssigue el mismo schema usado por el componenteDynamicFormdel frontend. Consulta Campos de formulario dinámico para la referencia completa de propiedades y ejemplos listos para copiar.
Ejemplo de petición:
{
"operation": "getDriverDetails",
"driver": "external",
"callbackUrl": "https://ticketz.example.com/subscription/webhook",
"currentSettings": {
"_externalEndpointUrl": "https://your-service.example.com/ticketz-payment-gateway",
"_externalAuthToken": "...",
"_externalCertFile": "1759325637143.pem"
},
"fileData": {
"_externalCertFile": "LS0tLS1CRUdJTi..."
}
}
Respuesta esperada:
{
"success": true,
"fields": [
{
"name": "tenantId",
"title": "Tenant ID",
"description": "Tenant devuelto por el flujo OAuth del proveedor",
"type": "text",
"lgWidth": 12,
"required": true
}
],
"modes": ["pix", "boleto", "cc", "link"],
"operations": [
"create",
"createSubscription",
"syncSubscriptionPayment",
"webhook",
"checkStatus",
"getSubscriptionStatus",
"getDriverDetails"
]
}
4.6 createSubscription
Llamada cuando _externalEnableSubscriptions es "true" y el usuario solicita un nuevo cobro. El payload de la petición es idéntico al de create, pero Ticketz espera un subscriptionId en la respuesta para poder casar futuras renovaciones de webhook con la empresa.
Respuesta esperada:
{
"success": true,
"paymentMethod": "credit_card",
"subscriptionId": "sub-external-123",
"txId": "external-tx-789",
"value": 99.9,
"checkoutUrl": "https://checkout.example.com/sub-external-123",
"payGwData": { "subscriptionId": "sub-external-123" }
}
La validación es similar a la de create, pero subscriptionId es obligatorio. Para suscripciones vía PIX y boleto, los datos del primer pago (qrcode o boletoUrl) también pueden devolverse para que el usuario pague de inmediato.
4.7 syncSubscriptionPayment
Llamada diariamente para toda factura abierta que tiene un subscriptionId, y también cuando el admin hace clic en Sincronizar pago en la lista de facturas. Tu endpoint debe consultar al proveedor sobre el próximo pago pendiente/futuro de la suscripción y devolver los datos pagables.
Respuesta esperada cuando existe un pago pendiente:
{
"success": true,
"paymentMethod": "pix",
"txId": "external-tx-renewal-456",
"value": 99.9,
"qrcode": { "qrcode": "00020126580014br.gov.bcb.pix..." },
"expireAt": "2026-07-17T23:59:59Z",
"payGwData": { "subscriptionId": "sub-external-123", "paymentId": "external-tx-renewal-456" }
}
Respuesta esperada cuando no hay pago pendiente disponible:
{
"success": true
}
Cuando se omite txId, Ticketz trata la respuesta como “sin datos disponibles” y deja la factura sin cambios. Cuando txId está presente, la respuesta se valida como una respuesta de create y la factura se actualiza con el nuevo txId, paymentMethod, payGwData y paymentData. Esto permite que el frontend muestre el cobro abierto (QR Code PIX, código de barras del boleto o link de checkout) en lugar de generar uno nuevo.
4.8 getSubscriptionStatus
Llamada diariamente para toda empresa que tiene una suscripción externa activa. Tu endpoint debe consultar al proveedor sobre el estado actual de la suscripción y devolver uno de los siguientes valores en el campo status:
active— la suscripción está activa; Ticketz mantiene la empresa marcada como suscriptora.inactive(ocanceled) — la suscripción ya no está activa; Ticketz actualiza la empresa localmente para que la generación de facturas vuelva al ciclo de cobranza regular.
Respuesta esperada:
{
"success": true,
"status": "active"
}
Devolver success: true sin el campo status, o devolver success: false, se trata como “estado desconocido” y la empresa se deja sin cambios.
5. Cabeceras de autenticación
Ticketz envía el token configurado en la cabecera Authorization:
Authorization: Bearer <token>
Tu endpoint debe validar este token y rechazar peticiones desconocidas.
6. Comportamiento de retry y timeout
- El driver usa un timeout fijo de 30 segundos.
- El driver hace hasta 3 intentos con retrasos de 1s, 3s y 5s.
- Los retries solo ocurren en fallos transitorios: errores de red, respuestas HTTP
5xxo HTTP429 Too Many Requests. - Las respuestas HTTP
4xxy las respuestas exitosas consuccess: falseno se reintentan. - Cada intento envía un nuevo
idempotencyKey(UUID v4); tu endpoint puede usarlo para evitar cobros duplicados. - Los webhooks se responden con
{ ok: true }al proveedor por defecto. Si tu respuesta dewebhookincluyewebhookResponse, Ticketz reenvía ese código de estado, cuerpo y cabeceras al proveedor.
7. Validación de respuesta
Ticketz valida cada respuesta exitosa antes de aplicarla. Si tu endpoint devuelve un payload malformado, la operación se aborta y el usuario ve un error ERR_EXTERNAL_*.
| Operación | Campos obligatorios en success: true |
|---|---|
create |
paymentMethod (pix, boleto o credit_card), txId no vacío, y los datos de pago exigidos por el modo (qrcode.qrcode para PIX, boletoUrl para boleto, o checkoutUrl para link). paymentData puede usarse en lugar de los campos del nivel superior. |
createSubscription |
paymentMethod, subscriptionId no vacío, y las mismas reglas de datos de pago de create. |
syncSubscriptionPayment |
Cuando txId está presente, misma validación que create. Cuando se omite txId, la respuesta significa “ningún pago pendiente disponible” y se acepta sin validación adicional. |
webhook |
status (paid, expired o ignored). paid/expired requieren txId y/o subscriptionId. |
checkStatus |
status (paid, expired o ignored). Una respuesta expired marca la factura como expired y registra expiredAt en paymentData. |
getSubscriptionStatus |
status (active, inactive o canceled). Otros valores se tratan como desconocidos. |
getDriverDetails |
fields, modes y operations deben ser arrays válidos. modes solo puede contener pix, boleto, cc o link. |
Cuando success: false, incluye errorCode y opcionalmente errorMessage. errorCode se reenvía al frontend y se traduce desde backendErrors.
8. Archivos de certificado privados
Si el driver declara un campo de archivo con extra.file.private: true, el usuario sube el archivo vía /settings/privateFile. El archivo se guarda en backend/private. En cada llamada RPC el driver lee el archivo y lo envía en fileData como string base64:
{
"fileData": {
"_externalCertFile": "LS0tLS1CRUdJTi..."
}
}
Tu endpoint puede usar este certificado para autenticación mTLS o para firmar peticiones al proveedor. No almacenes el archivo bruto en payGwData.
9. Idempotencia
Tu endpoint debe ser idempotente para create, especialmente porque Ticketz hace retry en fallo. Un patrón común es:
- Verificar si
txIdoinvoiceIdya tiene un cobro activo en tu proveedor. - Si sí, devolver los datos existentes (puedes setear
_reused: trueen la respuesta). - Si no, crear un nuevo cobro.
10. Ejemplo mínimo (Node.js / Express)
const express = require("express");
const app = express();
app.use(express.json());
const TOKEN = process.env.TICKETZ_GATEWAY_TOKEN;
function authorize(req) {
const header = req.headers.authorization || "";
return header === `Bearer ${TOKEN}`;
}
app.post("/ticketz-payment-gateway", async (req, res) => {
if (!authorize(req)) return res.status(401).json({ success: false });
const {
operation,
invoiceId,
txId,
price,
paymentMethod,
mode,
cardData,
webhookBody,
} = req.body;
if (operation === "create") {
const charge = await myProvider.createCharge({
amount: price,
method: paymentMethod,
mode,
cardData,
externalReference: String(invoiceId),
});
return res.json({
success: true,
paymentMethod,
txId: charge.id,
value: price,
qrcode: mode === "pix" ? { qrcode: charge.pixCopiaECola } : undefined,
boletoUrl: mode === "boleto" ? charge.pdfUrl : undefined,
boletoBarcode: mode === "boleto" ? charge.barcode : undefined,
checkoutUrl: mode === "link" ? charge.checkoutUrl : undefined,
expireAt: mode === "pix" ? charge.expiresAt : undefined,
dueDate: mode === "boleto" ? charge.dueDate : undefined,
});
}
if (operation === "webhook") {
const event = webhookBody?.event;
const data = webhookBody?.data || {};
if (event === "payment.received" && data.status === "paid") {
return res.json({
success: true,
status: "paid",
txId: data.txId,
value: data.amount,
});
}
return res.json({ success: true, status: "ignored" });
}
if (operation === "checkStatus") {
const status = await myProvider.checkCharge(txId);
return res.json({ success: true, status });
}
if (operation === "createSubscription") {
const subscription = await myProvider.createSubscription({
amount: price,
method: paymentMethod,
mode,
cardData,
externalReference: String(invoiceId),
});
return res.json({
success: true,
paymentMethod,
subscriptionId: subscription.id,
txId: subscription.firstPaymentId,
value: price,
checkoutUrl: subscription.checkoutUrl,
});
}
if (operation === "getDriverDetails") {
return res.json({
success: true,
fields: [
{
name: "providerAccountId",
title: "Provider Account ID",
type: "text",
lgWidth: 12,
required: true,
},
],
modes: ["pix", "boleto", "cc", "link"],
operations: [
"create",
"createSubscription",
"webhook",
"checkStatus",
"getDriverDetails",
],
});
}
return res
.status(400)
.json({ success: false, errorMessage: "Unknown operation" });
});
app.listen(3000);
11. Integración con el frontend
La pantalla de configuración del Gateway de Pago carga la lista de drivers en GET /payment-gateways/drivers. Para el driver external renderiza los campos base más cualquier campo dinámico devuelto por getDriverDetails. Los campos de archivo usan la subida existente en /settings/privateFile, y los campos de acción disparan POST /payment-gateways/external/fields, que devuelve los campos en caché. Para forzar un refresh de los metadatos en caché, un super usuario puede llamar a POST /payment-gateways/external/refresh-cache.
El checkout carga el driver activo en GET /payment-gateways/active y renderiza un selector de radio para cada modo soportado devuelto en supportedModes, excepto link. El modo link nunca se muestra como opción seleccionable; si es el único modo soportado por el driver activo, el checkout crea el cobro y redirige al usuario al checkoutUrl devuelto por el driver.
Cuando solo está disponible un modo que no sea link, el selector de radio se oculta y el checkout avanza automáticamente a ese método:
pix/boleto— el cobro se crea de inmediato y el usuario es llevado a la pantalla de éxito.cc— elCreditCardFormse muestra y el usuario completa los datos de la tarjeta antes de hacer clic en Pagar.
Para el modo cc el CreditCardForm nativo captura los datos de la tarjeta y los envía en cardData. Para el modo link el frontend redirige al checkoutUrl devuelto por el driver.
Reutilizando cobros abiertos existentes
Cuando una factura abierta ya tiene paymentData con QR Code, URL de boleto o link de checkout, la lista de facturas muestra los botones Ver Pix, Ver Boleto o Pagar Externo en lugar de generar un cobro duplicado. Hacer clic en Pagar en tal factura primero llama a POST /invoices/:id/check-payment para confirmar que el cobro sigue siendo válido:
- Si el cobro está
paid→ la factura se marca como pagada y el usuario ve un toast de confirmación. - Si el cobro está
expired→ los datos del cobro se limpian y el checkout abre para que el usuario genere un nuevo cobro. - Si el cobro sigue abierto → un modal muestra el QR Code PIX, código de barras del boleto o link de checkout existente.
12. Troubleshooting
| Síntoma | Causa probable |
|---|---|
ERR_EXTERNAL_ENDPOINT_NOT_CONFIGURED |
_externalEndpointUrl está vacío. |
ERR_EXTERNAL_RPC_CALL_FAILED |
Error de red o tu endpoint devolvió 5xx tras los retries. |
| Webhook no dispara | El proveedor está llamando a la URL equivocada; verifica callbackUrl y reglas de firewall. |
| Factura no marcada como pagada | Tu respuesta de webhook no incluyó status: "paid" más un txId válido, o el value pagado es menor que el valor de la factura. |
ERR_EXTERNAL_MISSING_PAYMENT_DATA |
Respuesta de create sin qrcode para PIX, boletoUrl para boleto, o checkoutUrl para link (o el equivalente dentro de paymentData). |
ERR_EXTERNAL_INVALID_FIELDS |
Array fields de getDriverDetails inválido o un campo sin name, title, type o required. |
ERR_EXTERNAL_INVALID_STATUS |
Respuesta de webhook o checkStatus con status distinto de paid, expired o ignored. |
ERR_EXTERNAL_INVALID_MODES |
Array modes de getDriverDetails no es un array o contiene valores distintos de pix, boleto, cc o link. |
ERR_EXTERNAL_INVALID_OPERATIONS |
Array operations de getDriverDetails no es un array. |
ERR_EXTERNAL_MISSING_SUBSCRIPTION_ID |
Respuesta de createSubscription no incluyó un subscriptionId no vacío. |
| Create siempre crea cobros duplicados | Tu endpoint no es idempotente; verifica invoiceId/txId antes de crear. |
| Modo no aparece en el checkout | El modo está deshabilitado por _externalDisable* o no fue devuelto por getDriverDetails.modes. |
| Suscripción no creada | _externalEnableSubscriptions no es "true" o getDriverDetails.operations no incluye createSubscription. |
13. Checklist de seguridad
- Usa HTTPS para la URL del endpoint y del webhook.
- Valida el token de autorización en cada petición.
- Valida las firmas de webhook del proveedor dentro de tu endpoint.
- Trata
invoiceIdytxIdcomo strings opacos. - No expongas credenciales internas del proveedor en
payGwData.