Documentación pública
Documentación API
Integrar Payzen en backends, ERP y automatizaciones. No necesitas iniciar sesión para leer esta guía; para crear claves API o webhooks, usa el panel (enlace arriba).
¿Por dónde empiezo?
Elige según dónde vive tu código: servidor con clave API o web con checkout embebido.
Backend / ERP / automatización
API REST con X-Api-Key
Clientes, suscripciones, facturas, pagos y webhooks salientes. Ir a autenticación (sección 1).
Web / SaaS con checkout embebido
Clave publicable, SDK y /api/v1/public/…
Quickstart con curl y luego la sección Integración web (SDK).
Capa pública
Quickstart: primer cobro embebido
- En el panel, crea una app de integración y una tabla de precios con los planes que quieras vender.
- Desde tu servidor, crea una sesión de checkout (secreto +
Idempotency-Key):curl -sS -X POST "https://api.payzen.cl/api/v1/public/checkout_sessions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer pz_sec_TU_SECRETO" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "price_id": "PRICE_ID", "external_customer_id": "TU_USER_ID", "customer_email": "[email protected]", "customer_name": "Nombre Usuario", "success_url": "https://tuapp.com/billing/success", "cancel_url": "https://tuapp.com/billing/cancel" }' - Abre
checkout_urlde la respuesta o el flujo hosted que uses. - Consulta suscripciones del cliente (publicable con origen permitido, o secreto):
curl -sS "https://api.payzen.cl/api/v1/public/subscriptions/external_customer_id" \ -H "X-Payzen-Publishable-Key: pz_pub_TU_CLAVE"
- Mutaciones (cancelar, pausar, ítems): solo con secreto en backend, no con clave publicable:
curl -sS -X PATCH "https://api.payzen.cl/api/v1/public/subscriptions/external_customer_id/SUBSCRIPTION_UUID" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer pz_sec_TU_SECRETO" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"subscription":{"cancel_at_period_end":true}}'
⚠️ Importante: los parámetros van en la raíz del JSON, no envueltos en { "checkout_session": { ... } }. Envolver los parámetros causa que campos como external_customer_id se ignoren.
Seguridad: nunca expongas pz_sec_… en el navegador. Para CSP y embeds, ver Integración web (SDK).
Tabla de errores resumida debajo en la misma página; lista completa en public-subscription-integration.md. OpenAPI: tag PublicIntegration.
Integración en otras aplicaciones
Cómo conectar tu sistema a Payzen
Payzen expone una API REST JSON bajo /api/v1. Cada workspace (tu cuenta / empresa) tiene sus propios datos aislados: la clave API y la sesión siempre operan sobre un solo tenant.
URL base para tus llamadas: https://api.payzen.cl/api/v1
En producción sustituye por el dominio real de tu despliegue (p. ej. https://api.tudominio.com/api/v1). En local suele ser http://localhost:3001/api/v1. Si usas túnel HTTPS, define API_DOCS_BASE_URL en billing_ui para que el asistente muestre la URL pública correcta.
1. Autenticación
Desde servidor a servidor (recomendado): crea una clave en Nueva clave API y envía el valor completo en el encabezado:
X-Api-Key: <tu_prefix>.<secreto>
No expongas la clave en apps móviles ni en el navegador del cliente final. Úsala solo en backend, workers o funciones serverless.
Desde el panel (Next.js / cookie): el mismo usuario puede usar Authorization: Bearer sess_… o la cookie de sesión; eso es para la UI autenticada, no para integrar un ERP externo.
2. Convenciones HTTP
Content-Type: application/jsonen cuerpos POST / PATCH.- Respuestas con envoltorio
{ "data": … }en recursos principales; errores con{ "error": { "code", "message" } }. - Idempotencia: en operaciones que crean cobros o efectos laterales (p. ej.
POST …/payments/:id/attempt), envía un UUID único enIdempotency-Keypara poder reintentar sin duplicar el intento. - Fechas en ISO 8601 (UTC) salvo que el campo indique lo contrario.
3. Flujo típico de facturación
- Cliente:
POST /api/v1/customers(o reutiliza uno existente buscando por email). - Suscripción:
POST /api/v1/subscriptionsconitems: [{ price_id, quantity }]. - Factura:
POST /api/v1/invoicesconcustomer_id,subscription_ide ítems. - Pago:
POST /api/v1/paymentsconinvoice_id, luegoPOST /api/v1/payments/:id/attemptconIdempotency-Keypara iniciar el cobro con el proveedor (Mercado Pago, Transbank, etc.). - Consulta el estado con
GET /api/v1/payments/:ido espera el webhook que Payzen envía a tu URL (ver abajo).
curl -sS -X GET "https://api.payzen.cl/api/v1/customers" \ -H "Accept: application/json" \ -H "X-Api-Key: sk_live_tu_clave"
4. Checkout público (sin clave en el navegador)
Para que un comprador pague sin iniciar sesión en Payzen, usa el enlace de checkout del plan (token firmado ck). El flujo usa endpoints públicos bajo /api/v1/public/checkout/… (sin X-Api-Key en el cliente final).
Opcionalmente puedes configurar API_KEY en el servidor del checkout Next solo para ciertos flujos; la documentación detallada está en el asistente de inicio.
Sitio web y SPA
5. Integración web (SDK y API pública)
Para mostrar tabla de precios, checkout y portal del cliente dentro de tu web sin exponer X-Api-Key en el navegador, usa una app de integración: clave publicable en el front y secreto solo en tu backend.
En el panel: Apps de integración (crear app, copiar publishable key, definir allowed_origins) y tabla de precios (slug + planes).
Cabeceras
- Navegador / SDK:
X-Payzen-Publishable-Key: pz_pub_… - Tu servidor (crear sesiones): mismo publishable key o firma según endpoint; el secreto pz_sec_… solo en entorno servidor para
POST …/public/checkout_sessionsyPOST …/public/portal_sessionscuando la API lo exija.
Script del SDK (sirve desde el host del panel UI, no desde la API): https://www.payzen.cl/sdk/payzen.js (en local: http://localhost:3000/sdk/payzen.js).
<script src="https://www.payzen.cl/sdk/payzen.js" async></script>
<script>
Payzen.init({ publishableKey: 'pz_pub_…', apiBase: 'https://api.payzen.cl/api/v1' });
Payzen.mountPricingTable('#pricing', { pricingTableSlug: 'mi-tabla' });
</script>Páginas alojadas en Payzen (token de sesión): checkout /integrations/checkout/<token>, portal /integrations/portal/<token>. Tu backend crea la sesión y redirige o abre iframe según tu UX.
Endpoints públicos (resumen, bajo https://api.payzen.cl/api/v1/public/…):
- GET public/pricing_tables/:slug
- POST public/checkout_sessions · GET public/checkout_sessions/:id
- POST public/portal_sessions · GET public/portal/:token/context
- GET public/prices · GET public/subscriptions/:external_customer_id
- PATCH public/subscriptions/:external_customer_id/:subscription_id — actualizar suscripción solo desde tu servidor con secreto o token firmado (no uses la clave publicable aquí). Opcional
Idempotency-Key.
Errores frecuentes (API pública)
public_auth_missing- Falta cabecera válida (publishable, secreto o token firmado).
invalid_publishable_key- Clave incorrecta o app deshabilitada.
invalid_secret_key- Secreto incorrecto.
origin_not_allowed- Añade el origen exacto en allowed_origins.
rate_limited- 429 — reintenta con backoff.
idempotency_key_reused- 409 — misma clave con otro cuerpo.
customer_not_found- external_id del cliente no existe en Payzen.
subscription_not_found- UUID no coincide con el cliente.
price_not_found- price_id inactivo o ajeno al workspace.
CSP: permite conexiones a la API (connect-src) y carga del script (script-src). Alinea allowed_origins con el origen exacto de tu sitio ( esquema + host + puerto).
Más detalle: public-subscription-integration.md
6. Webhooks (Payzen → tu aplicación)
Payzen envía notificaciones HTTP POST a tu servidor cada vez que ocurre un evento relevante (pago aprobado, suscripción cancelada, etc.). Esto permite que tu sistema reaccione en tiempo real sin tener que consultar la API periódicamente.
6.1 Flujo general
- Registra una URL HTTPS pública en Webhooks → Nuevo. Selecciona los eventos que te interesan (o déjalos vacíos para recibirlos todos).
- Payzen genera un secreto único para ese endpoint (visible solo al crearlo).
- Cuando ocurre un evento, Payzen crea un
Eventy el Dispatcher busca endpoints activos cuya lista de eventos coincida. - Se encola un
WebhookDeliveryJobque hace POST a tu URL con el payload JSON firmado. - Si tu servidor responde 2xx, la entrega queda como
delivered. Si falla, se reintenta hasta 5 veces con backoff progresivo.
6.2 Eventos disponibles
Pagos
- payment.created
- payment.attempted
- payment.succeeded
- payment.failed
- payment.refunded
Facturas
- invoice.created
- invoice.paid
- invoice.failed
Suscripciones
- subscription.created
- subscription.updated
- subscription.canceled
Diagnóstico
- webhook.test
6.3 Payload del POST
Cada entrega envía un POST con estas cabeceras y cuerpo:
POST https://tu-servidor.com/webhook
Content-Type: application/json
X-Billing-Signature: t=1712800000,v1=a1b2c3d4...
Idempotency-Key: <event_id>
{
"id": "evt_abc123",
"type": "payment.succeeded",
"data": {
"payment_id": "pay_xyz",
"amount": 15000,
"currency": "CLP",
"customer_id": "cus_456"
},
"occurred_at": "2026-04-10T20:00:00Z"
}6.4 Verificación de firma
La cabecera X-Billing-Signature tiene formato t=<timestamp>,v1=<hmac_hex>. Para verificar:
- Extrae
t(epoch en segundos) yv1(firma hex). - Rechaza si
|now - t|> 300 s (tolerancia de 5 minutos). - Concatena:
"{t}.{cuerpo_raw_json}". - Calcula
HMAC-SHA256(secreto_endpoint, cadena). - Compara con
v1usando comparación de tiempo constante.
# Ejemplo en Ruby
sig = request.headers["X-Billing-Signature"]
match = sig.match(/t=(\d+),v1=([a-f0-9]+)/i)
timestamp = match[1]
received_hmac = match[2]
data = "#{timestamp}.#{request.raw_post}"
expected = OpenSSL::HMAC.hexdigest("SHA256", ENV["WEBHOOK_SECRET"], data)
if ActiveSupport::SecurityUtils.secure_compare(expected, received_hmac)
# Firma válida → procesar evento
else
head :unauthorized
end# Ejemplo en Node.js / Express
const crypto = require("crypto");
app.post("/webhook", (req, res) => {
const sig = req.headers["x-billing-signature"];
const [, t, v1] = sig.match(/t=(\d+),v1=([a-f0-9]+)/i) || [];
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
return res.status(401).send("Timestamp expired");
}
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(t + "." + req.rawBody)
.digest("hex");
if (expected !== v1) return res.status(401).send("Invalid signature");
// Procesar evento...
res.status(200).json({ ok: true });
});6.5 Reintentos y backoff
Si tu servidor no responde 2xx, Payzen reintenta automáticamente:
| Intento | Espera | Tiempo acumulado |
|---|---|---|
| 1° | 5 min | 5 min |
| 2° | 30 min | 35 min |
| 3° | 2 h | ~2.5 h |
| 4° | 6 h | ~8.5 h |
| 5° (último) | 24 h | ~32.5 h |
Tras 5 intentos fallidos la entrega queda como failed. Puedes reintentar manualmente desde el panel de entregas.
6.6 Bearer opcional
Si tu endpoint requiere autenticación adicional (responde 401 sin credenciales), puedes configurar un token Bearer al crear o editar el webhook. Payzen enviará la cabecera Authorization: Bearer <token> junto con la firma. El token se guarda cifrado y no se muestra en la respuesta de la API.
6.7 Endpoints de la API
| Método | Ruta | Descripción |
|---|---|---|
| GET | /api/v1/webhook_endpoints | Listar endpoints |
| POST | /api/v1/webhook_endpoints | Crear endpoint |
| PATCH | /api/v1/webhook_endpoints/:id | Actualizar endpoint |
| DELETE | /api/v1/webhook_endpoints/:id | Deshabilitar endpoint |
| POST | /api/v1/webhook_endpoints/:id/test | Enviar evento de prueba |
| GET | /api/v1/webhook_deliveries | Listar entregas |
| POST | /api/v1/webhook_deliveries/:id/retry | Reintentar entrega fallida |
Scope requerido: webhooks:read para consultar, webhooks:write para crear/editar/probar.
6.8 Mejores prácticas
- Responde 200 rápidamente y procesa el evento en segundo plano (cola, worker).
- Usa el campo
iddel evento como clave de idempotencia: si ya procesaste ese ID, ignora duplicados. - Valida siempre la firma antes de confiar en el contenido del POST.
- Usa HTTPS con certificado válido; Payzen rechaza URLs HTTP o IPs privadas.
- No expongas la misma ruta a autenticación de sesión del usuario; la verificación debe ser solo por firma.
No confundir con los webhooks que envía Mercado Pago a Payzen: esos van a la ruta de proveedor en la API de Payzen y usan la firma x-signature de MP.
7. Referencia OpenAPI y repositorio
La referencia completa de paths, esquemas y códigos de error está en el archivo OpenAPI del monorepo:
billing_platform/docs/openapi/openapi.yaml
Incluye la API clásica (X-Api-Key) y la capa pública de integración bajo /api/v1/public/… (tag PublicIntegration). Puedes importarlo en Postman, Insomnia o generar un SDK con openapi-generator.
Guías Markdown: billing_platform/docs/integration-external-apps.md (API servidor) y public-subscription-integration.md (SDK / público).
8. MCP (Cursor y otros clientes)
El monorepo incluye un servidor Model Context Protocol (stdio) en mcp-server/: expone esta guía y consultas al OpenAPI como herramientas para asistentes (p. ej. Cursor).
- Instalación: en el repo,
cd mcp-server && npm install && npm run build. - Variable opcional:
PAYZEN_REPO_ROOTcon la ruta absoluta al monorepo; si no se define, se infiere desde la ubicación del paquete. - Herramientas:
payzen_integration_guide,payzen_openapi_summary,payzen_openapi_list_paths,payzen_openapi_get_operation. - Cursor: en ajustes MCP, comando
nodecon argumento la ruta amcp-server/dist/index.js(tras el build). Detalle y ejemplos JSON enmcp-server/README.mddel repositorio.
9. Buenas prácticas
- Claves con el menor alcance (scopes) posible y rotación periódica.
- Timeouts y reintentos con backoff; respeta
429si existiera rate limit. - Registra
X-Request-Ido el cuerpo de error para soporte. - En producción usa siempre HTTPS y certificados válidos en tu endpoint de webhook.
6. Templates por framework
Integración rápida
Snippets copy-paste para los frameworks más populares.
// app/api/checkout/route.ts
import { PayzenClient } from "@payzen/node";
const payzen = new PayzenClient({
secretKey: process.env.PAYZEN_SECRET_KEY!,
apiBaseUrl: process.env.PAYZEN_API_BASE_URL,
});
export async function POST(req: Request) {
const { priceId, customerId } = await req.json();
const session = await payzen.checkoutSessions.create({
priceId,
externalCustomerId: customerId,
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/billing/success`,
cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/billing`,
});
return Response.json({ checkoutUrl: session.checkout_url });
}Resumen rápido
Endpoints frecuentes
GET /api/v1/customers
Listar o filtrar clientes del workspace.
POST /api/v1/subscriptions
Crear suscripción con precios del catálogo.
POST /api/v1/invoices
Emitir factura ligada a suscripción.
POST /api/v1/payments/:id/attempt
Ejecutar cobro con proveedor configurado.
GET /api/v1/webhook_endpoints
Consultar URLs registradas para eventos.
GET /api/v1/plans/:id/checkout_link
Obtener enlace firmado de pago para un plan.
GET /api/v1/public/payments/:id
Verificar estado de un pago tras checkout.
GET /api/v1/public/subscriptions/:ext_id
Suscripciones de un cliente por external_id.
SDK + GET /api/v1/public/pricing_tables/:slug
Integración en tu web: sección 5 · Integración web (SDK).
API pública de integración: OpenAPI tag PublicIntegration · public-subscription-integration.md
¿Necesitas operar en el panel? Inicia sesión
