> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplepay.mx/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Configura endpoints webhook en SimplePay, verifica firmas HMAC, procesa eventos de forma idempotente y usa replays para recuperarte.

Los webhooks son la fuente de verdad para estado post-pago y Bookable lifecycle. Un redirect puede fallar, cerrarse o llegar antes que la confirmacion final. El webhook es el mecanismo correcto para actualizar tu sistema.

## Crear endpoint

```bash terminal theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X POST "https://api.simplepay.mx/v1/webhook_endpoints" \
  -H "Authorization: Bearer $SIMPLEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: webhook_endpoint_prod_v1" \
  -d '{
    "url": "https://api.example.com/webhooks/simplepay",
    "description": "Production webhook endpoint",
    "enabled_events": [
      "booking.hold_created",
      "booking.confirmed",
      "booking.expired",
      "booking.cancelled"
    ]
  }'
```

Requisitos de URL:

* Debe ser HTTPS en produccion.
* No puede incluir username o password.
* No debe apuntar a redes locales o privadas.
* El hostname debe resolver publicamente.

## Payload

SimplePay envia:

```json webhook-payload theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "spwevt_0123456789abcdef0123456789abcdef",
  "type": "booking.confirmed",
  "data": {
    "booking": {
      "id": "00000000-0000-4000-8000-000000000010",
      "status": "confirmed",
      "paymentStatus": "paid"
    }
  }
}
```

## Firma

Cada delivery incluye:

```http header theme={"theme":{"light":"github-light","dark":"github-dark"}}
SimplePay-Signature: t=1782000000,v1=hex_hmac_sha256
```

La firma es HMAC SHA-256 sobre:

```txt signed-payload theme={"theme":{"light":"github-light","dark":"github-dark"}}
{timestamp}.{raw_json_payload}
```

Verificacion en Node:

```ts verify-simplepay-webhook.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import crypto from "node:crypto";

export function verifySimplePayWebhook(input: {
  rawBody: string;
  signatureHeader: string | null;
  secret: string;
  toleranceSeconds?: number;
}) {
  if (!input.signatureHeader) {
    throw new Error("Missing SimplePay-Signature header");
  }

  const parts = Object.fromEntries(
    input.signatureHeader.split(",").map((part) => {
      const [key, value] = part.split("=");
      return [key, value];
    }),
  );

  const timestamp = Number(parts.t);
  const signature = parts.v1;

  if (!timestamp || !signature) {
    throw new Error("Invalid SimplePay signature header");
  }

  const toleranceSeconds = input.toleranceSeconds ?? 300;
  const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp);

  if (age > toleranceSeconds) {
    throw new Error("SimplePay webhook timestamp outside tolerance");
  }

  const expected = crypto
    .createHmac("sha256", input.secret)
    .update(`${timestamp}.${input.rawBody}`)
    .digest("hex");

  if (
    !crypto.timingSafeEqual(
      Buffer.from(expected, "hex"),
      Buffer.from(signature, "hex"),
    )
  ) {
    throw new Error("Invalid SimplePay webhook signature");
  }
}
```

<Warning>
  Verifica la firma usando el raw body exacto que recibio tu servidor. Si
  parseas y reserializas JSON antes de verificar, la firma puede dejar de
  coincidir.
</Warning>

## Handler recomendado

<Steps>
  <Step title="Lee raw body">
    Necesitas los bytes o string exacto para verificar firma.
  </Step>

  <Step title="Verifica firma y timestamp">
    Rechaza requests sin firma valida.
  </Step>

  <Step title="Deduplica por event ID">
    Guarda `event.id` antes de ejecutar side effects.
  </Step>

  <Step title="Procesa por tipo">
    Actualiza orden, invoice o booking segun `type`.
  </Step>

  <Step title="Responde 2xx rapido">
    Si necesitas trabajo pesado, encolalo despues de persistir el evento.
  </Step>
</Steps>

## Listar endpoints

```bash terminal theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS "https://api.simplepay.mx/v1/webhook_endpoints" \
  -H "Authorization: Bearer $SIMPLEPAY_API_KEY"
```

## Replay

```bash terminal theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X POST "https://api.simplepay.mx/v1/events/spwevt_0123456789abcdef0123456789abcdef/replay" \
  -H "Authorization: Bearer $SIMPLEPAY_API_KEY"
```

<Tip>
  Para Bookable Payments, tu webhook debe poder recibir eventos despues del
  redirect. Muestra "confirmando reserva" hasta que el webhook o API read
  confirme `booking.confirmed`.
</Tip>
