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

# POST /webhooks/custom — Webhook de leads entrantes | DeltaLead

> Envío de un lead entrante a DeltaLead con una clave bearer. Cubre autenticación, el contrato de lead v1, idempotencia, límites y cada código de respuesta.

El Custom Webhook recibe un lead enviado a DeltaLead desde un sistema externo. Una sola ruta, un solo propósito, una sola cabecera obligatoria. Para crear claves desde el panel y entender la diferencia entre modos, ver la [guía del Custom Webhook](/integrations/custom-webhook).

## Endpoint

```text theme={null}
POST https://integrations.deltalead.ai/webhooks/custom
```

## Autenticación

La clave se envía como bearer token:

```text theme={null}
Authorization: Bearer dl_{live|sandbox}_{random}
```

El esquema se compara sin distinguir mayúsculas y el token se recorta, de modo que `bearer  dl_live_abc ` también se acepta. La clave identifica tanto la cuenta como el modo: ningún otro dato de la solicitud los determina.

<Note>
  Una cabecera ausente, una cabecera mal formada, una clave desconocida, una clave revocada y una clave vencida devuelven **la misma** respuesta `401 unauthorized`. Los cuerpos son idénticos a propósito: el error no permite deducir si una clave existe.
</Note>

## Cuerpo de la solicitud

El cuerpo es un objeto JSON con una única clave `lead`. El contrato de lead v1 exige `lead.name` **y al menos uno** de `lead.email` o `lead.phone`. Si falta cualquiera de las dos condiciones, la respuesta es `400 missing_required_fields`.

<ParamField body="lead.name" type="string" required>
  Nombre completo del lead. DeltaLead lo divide en el **primer espacio**: todo lo anterior pasa a ser el nombre y todo lo posterior, el apellido. `"María García"` produce `María` / `García`, y `"Probe Alpha Betamax"` produce `Probe` / `Alpha Betamax`.

  La división es posicional, no lingüística, así que un nombre compuesto termina en el campo de apellido: `"María José García"` produce `María` / `José García`. Si el sistema de origen ya guarda nombre y apellido por separado, conviene unirlos con un solo espacio en ese orden en lugar de enviar un nombre para mostrar.
</ParamField>

<ParamField body="lead.email" type="string">
  Dirección de correo. Obligatoria salvo que se envíe `lead.phone`.
</ParamField>

<ParamField body="lead.phone" type="string">
  Número de teléfono. Obligatorio salvo que se envíe `lead.email`. Se recomienda el formato E.164, por ejemplo `+5491155551234`.
</ParamField>

<ParamField body="lead.source" type="string">
  Etiqueta libre que indica el origen del envío, por ejemplo `landing-page`. Queda registrada en el lead y la usa el procesamiento posterior: aparece en el Resumen de IA del lead. Todo envío por este endpoint se reporta en el lead con el canal `Custom`, sin importar qué se envíe aquí, así que `source` sirve para distinguir cuál de los sistemas propios generó el lead.
</ParamField>

<ParamField body="lead.vehicle_of_interest" type="string">
  Descripción libre del vehículo por el que consulta el lead, por ejemplo `Toyota Hilux SRX 2024`. Queda registrada en el lead y la usa el Resumen de IA para buscar coincidencias en el stock del catálogo, así que conviene enviar lo que escribió el comprador y no un código interno de stock.
</ParamField>

<ParamField body="lead.notes" type="string">
  Contexto libre del lado del integrador. Se agrega a las **Notas** del lead, atribuido al webhook y visible para el asesor. Se deduplica por envío, de modo que un reintento no agrega la misma nota dos veces.
</ParamField>

<ParamField body="lead.metadata" type="object">
  Objeto JSON arbitrario que se guarda en el lead como passthrough y se combina entre envíos en lugar de sobrescribirse. Sirve para identificadores del sistema de origen que no tienen lugar en el contrato. DeltaLead no lo interpreta y nunca se le muestra al agente de IA. Las claves que contienen `.` o `$` se rechazan.
</ParamField>

<Warning>
  Estos siete campos son todo el contrato. Cualquier otra clave dentro de `lead` se acepta —la solicitud igual devuelve `200`— pero se **descarta** y nunca llega al registro del lead. Todo lo que quede fuera del contrato va en `metadata`, el único campo que acepta una forma arbitraria.
</Warning>

## Idempotencia

La cabecera opcional `Idempotency-Key` vuelve seguros los reintentos:

```text theme={null}
Idempotency-Key: 8f14e45f-ea67-4f6a-b3c1-9c1e2d0b7a52
```

Un envío que trae una clave ya vista para la cuenta devuelve `200 {"status": "duplicate"}` y no crea nada.

Las claves de idempotencia tienen alcance de **cuenta**, no de modo. Un valor ya usado con una clave sandbox suprime ese mismo valor cuando después llega con una clave live, así que no conviene arrastrar los valores de prueba al cambiar de modo.

<Warning>
  Sin `Idempotency-Key`, el endpoint no detecta repeticiones. Todo envío se acepta y devuelve `{"status": "ok"}`, incluso uno idéntico byte a byte al anterior. Conviene mandar la cabecera en cualquier solicitud que el cliente pueda reintentar: es la única forma de que una repetición se informe en lugar de aceptarse en silencio.
</Warning>

La clave debe derivarse de algo estable del sistema de origen, como el ID de envío del formulario que generó el lead. Reutilizar ese valor en cada reintento del mismo envío es lo que vuelve seguro el reintento.

### Coincidencia de contactos

Hay dos mecanismos de deduplicación, en capas distintas, y conviene no confundirlos.

`Idempotency-Key` deduplica **envíos**. Una repetición se rechaza antes de hacer cualquier trabajo y se informa como `{"status": "duplicate"}`.

La coincidencia de contactos deduplica **personas**. Una vez aceptado el envío, DeltaLead lo compara con los contactos existentes por correo o teléfono. Una coincidencia se resuelve sobre el lead existente y lo enriquece en lugar de crear un segundo: un envío que solo trae teléfono puede sumarse a un lead creado antes a partir de un correo, y el contacto termina con ambos datos.

La consecuencia práctica es que un envío que coincide con un contacto existente igual devuelve `{"status": "ok"}`, no `duplicate`. El `200` indica que el envío fue aceptado; nunca indica si se creó un lead nuevo.

<Note>
  La coincidencia es por correo **o** teléfono, no por ambos. Dos personas realmente distintas que comparten un teléfono —un hogar, o la central de un concesionario— se resuelven sobre el mismo lead. Conviene enviar los datos de contacto más específicos disponibles.
</Note>

## Límites

| Límite            | Valor                     |
| ----------------- | ------------------------- |
| Tamaño del cuerpo | 256 KB                    |
| Tasa sostenida    | 5 solicitudes por segundo |
| Ráfaga            | 10 solicitudes            |

Los límites de tasa se aplican por ruta.

## Respuestas

Un envío exitoso devuelve un `status`:

```json 200 OK theme={null}
{"status": "ok"}
```

Un fallo devuelve un sobre de error, donde `detail` identifica la causa:

```json 401 Unauthorized theme={null}
{"status": "error", "detail": "unauthorized"}
```

| Estado | `status` / `detail`       | Cuándo                                                                                |
| ------ | ------------------------- | ------------------------------------------------------------------------------------- |
| `200`  | `ok`                      | El envío fue aceptado para procesar                                                   |
| `200`  | `duplicate`               | La `Idempotency-Key` ya se había visto                                                |
| `400`  | `invalid_body`            | No se pudo leer el cuerpo                                                             |
| `400`  | `invalid_json`            | El cuerpo está vacío, no es JSON válido o no es un objeto JSON                        |
| `400`  | `missing_required_fields` | No se cumple el contrato de lead v1, incluido un `lead` ausente o que no es un objeto |
| `400`  | `payload_too_large`       | El cuerpo supera los 256 KB                                                           |
| `401`  | `unauthorized`            | Cualquier fallo de autenticación                                                      |
| `500`  | `enqueue_failed`          | Aceptado, pero no se pudo encolar                                                     |

La ruta acepta únicamente `POST`. Cualquier otro método, o cualquier otra ruta en este host, devuelve `404 {"message": "Not Found"}` desde el gateway, antes de que corra el handler del webhook.

Un `200` confirma que DeltaLead aceptó el envío, no que el lead exista. El lead se crea de forma asíncrona unos segundos después.

<Tip>
  Un `500 enqueue_failed` se puede reintentar: el envío era válido y el fallo es del lado de DeltaLead. Ningún `4xx` debe reintentarse, porque la solicitud volverá a fallar igual. Conviene mandar siempre una `Idempotency-Key` en solicitudes que puedan reintentarse, ya que los reintentos solo se deduplican con esa cabecera.
</Tip>

## Ejemplos

Sandbox, la solicitud válida mínima:

```bash cURL theme={null}
curl -X POST https://integrations.deltalead.ai/webhooks/custom \
  -H "Authorization: Bearer dl_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lead":{"name":"María García","email":"maria@example.com"}}'
```

Live, la misma solicitud con todos los campos:

```bash cURL theme={null}
curl -X POST https://integrations.deltalead.ai/webhooks/custom \
  -H "Authorization: Bearer dl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lead":{"name":"María García","email":"maria@example.com","phone":"+5491155551234","source":"landing-page","vehicle_of_interest":"Toyota Hilux SRX 2024","notes":"Consultó por financiación a 12 cuotas.","metadata":{"form_id":"web-123","campaign":"verano"}}}'
```

Live, con clave de idempotencia, seguro de reintentar:

```bash cURL theme={null}
curl -X POST https://integrations.deltalead.ai/webhooks/custom \
  -H "Authorization: Bearer dl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ea67-4f6a-b3c1-9c1e2d0b7a52" \
  -d '{"lead":{"name":"María García","phone":"+5491155551234","source":"landing-page"}}'
```
