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.

Guía completa en el repo: public-subscription-integration.md

Capa pública

Quickstart: primer cobro embebido

  1. En el panel, crea una app de integración y una tabla de precios con los planes que quieras vender.
  2. 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"
      }'
  3. Abre checkout_url de la respuesta o el flujo hosted que uses.
  4. 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"
  5. 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/json en 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 en Idempotency-Key para 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

  1. Cliente: POST /api/v1/customers (o reutiliza uno existente buscando por email).
  2. Suscripción: POST /api/v1/subscriptions con items: [{ price_id, quantity }].
  3. Factura: POST /api/v1/invoices con customer_id, subscription_id e ítems.
  4. Pago: POST /api/v1/payments con invoice_id, luego POST /api/v1/payments/:id/attempt con Idempotency-Key para iniciar el cobro con el proveedor (Mercado Pago, Transbank, etc.).
  5. Consulta el estado con GET /api/v1/payments/:id o 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_sessions y POST …/public/portal_sessions cuando 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

  1. Registra una URL HTTPS pública en Webhooks → Nuevo. Selecciona los eventos que te interesan (o déjalos vacíos para recibirlos todos).
  2. Payzen genera un secreto único para ese endpoint (visible solo al crearlo).
  3. Cuando ocurre un evento, Payzen crea un Event y el Dispatcher busca endpoints activos cuya lista de eventos coincida.
  4. Se encola un WebhookDeliveryJob que hace POST a tu URL con el payload JSON firmado.
  5. 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:

  1. Extrae t (epoch en segundos) y v1 (firma hex).
  2. Rechaza si |now - t| > 300 s (tolerancia de 5 minutos).
  3. Concatena: "{t}.{cuerpo_raw_json}".
  4. Calcula HMAC-SHA256(secreto_endpoint, cadena).
  5. Compara con v1 usando 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:

IntentoEsperaTiempo acumulado
5 min5 min
30 min35 min
2 h~2.5 h
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étodoRutaDescripción
GET/api/v1/webhook_endpointsListar endpoints
POST/api/v1/webhook_endpointsCrear endpoint
PATCH/api/v1/webhook_endpoints/:idActualizar endpoint
DELETE/api/v1/webhook_endpoints/:idDeshabilitar endpoint
POST/api/v1/webhook_endpoints/:id/testEnviar evento de prueba
GET/api/v1/webhook_deliveriesListar entregas
POST/api/v1/webhook_deliveries/:id/retryReintentar 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 id del 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_ROOT con 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 node con argumento la ruta a mcp-server/dist/index.js (tras el build). Detalle y ejemplos JSON en mcp-server/README.md del repositorio.

9. Buenas prácticas

  • Claves con el menor alcance (scopes) posible y rotación periódica.
  • Timeouts y reintentos con backoff; respeta 429 si existiera rate limit.
  • Registra X-Request-Id o 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.

$ npm install @payzen/js @payzen/node
// 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