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

# Recibir webhooks

> Suscríbete a eventos Clausum y verifica entregas firmadas.

Clausum notifica a tus sistemas resultados de assess, fraude, expedientes y disputas mediante solicitudes HTTP `POST` firmadas. Consulta [Eventos webhook](/es/concepts/webhook-events) para el catálogo completo de **15 eventos**.

## 1. Registrar un endpoint

**Panel (recomendado)**

1. Abre **Conexiones** → pestaña **Empezar** → sección **Webhooks salientes** (sandbox o producción).
2. Ingresa **URL HTTPS** y selecciona eventos (los 15 tipos disponibles).
3. Copia el **secreto de firma** mostrado una sola vez al crear.

**Nota de producción:** crear un webhook de **producción** puede requerir **credenciales live aprobadas** para tu organización. Los webhooks sandbox funcionan de inmediato con claves `clm_*_sbx_*`.

**API (opcional):** `GET/POST /api/v1/integrations/merchant-webhooks` con JWT de sesión del panel — misma lista de eventos que la UI.

## 2. Sandbox vs producción

| Entorno | Claves API | Entregas webhook |
| - | - | - |
| Sandbox | `clm_sk_sbx_*` / `clm_pub_sbx_*` | URLs + secretos separados por entorno |
| Producción | `clm_sk_*` / `clm_pub_*` | URLs + secretos separados |

Assess con claves sandbox dispara solo webhooks sandbox.

## 3. Entender la entrega

| Encabezado | Ejemplo | Descripción |
| - | - | - |
| `X-Clausum-Signature` | `t=1716998400,v1=8a9b...` | Firma HMAC |
| `X-Clausum-Event` | `transaction.created` | Tipo de evento |
| `X-Clausum-Timestamp` | `2026-07-05T18:30:00Z` | Hora de entrega |

Cuerpo:

```json theme={null}
{
  "event": "transaction.created",
  "timestamp": "2026-07-05T18:30:00.000Z",
  "data": {
    "session_id": "ps_...",
    "decision": "approve",
    "assess_response": { "decision": "approve", "session_id": "ps_..." }
  }
}
```

## 4. Verificar la firma

`HMAC_SHA256(secret, "<timestamp>.<rawBody>")` → encabezado `t=...,v1=<hex>`. **Verifica el cuerpo raw** antes de parsear JSON.

<CodeGroup>
  ```ts Node.js theme={null}
  import crypto from "crypto"

  export function verifyClausumSignature(rawBody: string, header: string, secret: string): boolean {
    const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
    const timestamp = parts["t"]
    const provided = parts["v1"]
    if (!timestamp || !provided) return false

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

    return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected))
  }
  ```

  ```python Python theme={null}
  import hmac, hashlib

  def verify_clausum_signature(raw_body: str, header: str, secret: str) -> bool:
      parts = dict(p.split("=", 1) for p in header.split(","))
      timestamp, provided = parts.get("t"), parts.get("v1")
      if not timestamp or not provided:
          return False
      expected = hmac.new(
          secret.encode(),
          f"{timestamp}.{raw_body}".encode(),
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(provided, expected)
  ```
</CodeGroup>

## 5. Manejar el evento

```ts theme={null}
export async function POST(req: Request) {
  const raw = await req.text()
  const signature = req.headers.get("x-clausum-signature")

  if (!signature || !verifyClausumSignature(raw, signature, process.env.CLAUSUM_WEBHOOK_SECRET!)) {
    return new Response("Invalid signature", { status: 401 })
  }

  const event = JSON.parse(raw)

  switch (event.event) {
    case "transaction.created":
    case "transaction.blocked":
      await syncOrderFromAssess(event.data)
      break
    case "fraud.detected":
      await onFraudDetected(event.data)
      break
    case "dispute.created":
      await onDispute(event.data)
      break
  }

  return new Response("ok", { status: 200 })
}
```

## Monitorear entregas

**Conexiones → Monitor → Entregas de webhooks salientes** muestra intentos recientes, estado HTTP y estado de reintento.

## Buenas prácticas

<AccordionGroup>
  <Accordion title="Verificar contra el cuerpo raw" icon="fingerprint">
    Lee el texto raw primero, verifica, luego parsea JSON.
  </Accordion>

  <Accordion title="Responder rápido" icon="bolt">
    Devuelve `2xx` en pocos segundos; usa una cola para trabajo pesado.
  </Accordion>

  <Accordion title="Ser idempotente" icon="rotate">
    Hay duplicados — clave por `session_id` + `event` o `case_id`.
  </Accordion>

  <Accordion title="Suscribirse de forma acotada" icon="filter">
    Empieza con `transaction.blocked`, `transaction.created` y `fraud.detected`; agrega eventos de disputa cuando tu flujo de chargeback esté listo.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.