Andorpay
Navegación de la documentación

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. 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. 2

    Abre el dashboard

    Entra en Webhooks y API keys, guarda la URL y elige los eventos que consume tu aplicación.

  3. 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. 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.

CabeceraContenido
Content-Typeapplication/json
User-AgentAndorpay-Webhooks/1.0
andorpay-event-idIdentificador estable de la entrega lógica.
andorpay-event-typeTipo del evento incluido en el cuerpo.
andorpay-signatureMarca temporal y firma HMAC SHA-256.
x-andorpay-flow-idCorrelación operativa del flujo.
x-request-idCorrelación de la petición de entrega.
Ejemplo payment.succeeded
json
{
  "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.

app/api/andorpay/webhook/route.ts
typescript
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

EventoUso
payment.succeededEl rail confirmó el pago. Puede activar fulfillment o acceso.
payment.failedEl intento terminó sin cobro. No entrega el producto.
invoice.createdAndorPay creó una factura asociada al cobro o periodo.
invoice.payment_failedEl cobro de la factura no pudo completarse.
subscription.createdSe creó una suscripción con su estado y periodo inicial.
subscription.updatedCambió el estado, periodo o configuración de una suscripción.
subscription.cancelledLa suscripción terminó o quedó cancelada.
payment_method.updatedEl cliente completó la actualización de su método de pago.
payment_method.update_failedLa actualización del método de pago terminó con un fallo.
payment_method.updated_requiredLa recurrencia necesita intervención del cliente sobre su método de pago.
billing.operational_alertExiste 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 siguienteEspera aproximada
Primero1 minuto
Segundo5 minutos
Tercero30 minutos
Cuarto2 horas
Quinto6 horas
Siguientes24 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.

Siguiente paso

    Usamos cookies necesarias para que la web funcione y, si aceptas, cookies de analítica para mejorarla. Consulta nuestra Política de cookies.