Documentación para desarrolladores
Webhooks
Usa los webhooks firmados como fuente de verdad para pagos, facturas, suscripciones y recuperación de cobros.
Configurar el endpoint
- 1
Publica una URL HTTPS
El endpoint debe aceptar POST desde Internet. No coloques una página de login, CAPTCHA ni redirección delante de la ruta. - 2
Abre el dashboard
Entra en Webhooks y API keys, guarda la URL y elige los eventos que consume tu aplicación.
- 3
Guarda el secreto
El secreto de firma se muestra al crearlo o rotarlo. Trátalo como un secreto de servidor separado de la API key. - 4
Comprueba una entrega
Realiza un pago de sandbox, abre su entrega en el dashboard y compara event.id, estado HTTP e intentos.
Contrato de entrega
AndorPay envía un evento por petición y utiliza semántica de entrega al menos una vez. Una reentrega del mismo evento conserva el mismo event.id. No existe un orden global entre tipos de evento ni entre operaciones independientes.
| Cabecera | Contenido |
|---|---|
Content-Type | application/json |
User-Agent | Andorpay-Webhooks/1.0 |
andorpay-event-id | Identificador estable de la entrega lógica. |
andorpay-event-type | Tipo del evento incluido en el cuerpo. |
andorpay-signature | Marca temporal y firma HMAC SHA-256. |
x-andorpay-flow-id | Correlación operativa del flujo. |
x-request-id | Correlación de la petición de entrega. |
{
"id": "evt_01JQ8R3D4P7N7QGJ9M2K3W8X6B",
"type": "payment.succeeded",
"occurred_at": "2026-08-23T10:42:18.000Z",
"merchant_id": "ap_mer_01JQ8ANDORPAY",
"customer": {
"id": "ap_cus_123",
"external_customer_id": "customer_42",
"email": "ana@example.com"
},
"product": {
"id": "ap_prod_starter_monthly"
},
"payment": {
"id": "ap_pay_123",
"rail": "redsys",
"status": "succeeded"
}
}Verificar la firma
Lee el cuerpo sin procesar como texto antes de interpretar JSON. La cabecera tiene el formato t=timestamp,v1=firmay firma la cadena timestamp.cuerpo_sin_procesar. Rechaza marcas temporales con más de 5 minutos de diferencia y compara la firma en tiempo constante.
import { createHmac, timingSafeEqual } from "node:crypto"
import { NextResponse } from "next/server"
export const runtime = "nodejs"
function validSignature(rawBody: string, header: string, secret: string) {
const values = new Map(header.split(",").map((part) => part.trim().split("=", 2)))
const timestamp = values.get("t")
const signature = values.get("v1")
if (!timestamp || !signature || !/^[a-f0-9]{64}$/i.test(signature)) return false
const timestampSeconds = Number(timestamp)
if (!Number.isFinite(timestampSeconds)) return false
if (Math.abs(Date.now() / 1000 - timestampSeconds) > 300) return false
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex")
const receivedBuffer = Buffer.from(signature, "hex")
const expectedBuffer = Buffer.from(expected, "hex")
return receivedBuffer.length === expectedBuffer.length && timingSafeEqual(receivedBuffer, expectedBuffer)
}
export async function POST(request: Request) {
const rawBody = await request.text()
const signature = request.headers.get("Andorpay-Signature")
const secret = process.env.ANDORPAY_WEBHOOK_SECRET
if (!signature || !secret || !validSignature(rawBody, signature, secret)) {
return NextResponse.json({ code: "invalid_signature" }, { status: 400 })
}
const event = JSON.parse(rawBody) as { id: string; type: string }
await acceptVerifiedAndorpayEvent(event)
return NextResponse.json({ received: true })
}Eventos configurables
| Evento | Uso |
|---|---|
payment.succeeded | El rail confirmó el pago. Puede activar fulfillment o acceso. |
payment.failed | El intento terminó sin cobro. No entrega el producto. |
invoice.created | AndorPay creó una factura asociada al cobro o periodo. |
invoice.payment_failed | El cobro de la factura no pudo completarse. |
subscription.created | Se creó una suscripción con su estado y periodo inicial. |
subscription.updated | Cambió el estado, periodo o configuración de una suscripción. |
subscription.cancelled | La suscripción terminó o quedó cancelada. |
payment_method.updated | El cliente completó la actualización de su método de pago. |
payment_method.update_failed | La actualización del método de pago terminó con un fallo. |
payment_method.updated_required | La recurrencia necesita intervención del cliente sobre su método de pago. |
billing.operational_alert | Existe una incidencia de billing que requiere atención operativa. |
Reintentos y respuestas
Cualquier estado HTTP entre 200 y 299 confirma que tu endpoint aceptó el evento. Los demás estados, errores de red y timeouts provocan un nuevo intento. AndorPay no sigue redirects y utiliza un timeout de 15 segundos por petición.
| Intento siguiente | Espera aproximada |
|---|---|
| Primero | 1 minuto |
| Segundo | 5 minutos |
| Tercero | 30 minutos |
| Cuarto | 2 horas |
| Quinto | 6 horas |
| Siguientes | 24 horas, hasta un máximo predeterminado de 15 intentos |
Procesar reentregas y fallos
Verifica primero la firma. Después usa event.id para reconocer una reentrega y evita ejecutar dos veces el mismo efecto de negocio. La técnica concreta depende de las garantías y almacenamiento de tu aplicación; no forma parte del contrato de AndorPay.
Si el efecto no puede aceptarse, devuelve un estado no 2xx para solicitar otro intento. Consulta el historial y utiliza el reintento manual del dashboard cuando hayas corregido la causa. Registra event.id,andorpay-event-type, x-request-id y el estado de respuesta sin guardar secretos ni datos sensibles de pago.