# Conectar con tu sistema

> Manda datos a tu CRM o tu tienda con el paso Solicitud HTTP, y arranca automatizaciones desde tu sistema con un webhook entrante.

Las automatizaciones se conectan con tus otros sistemas en los dos sentidos:

| | Qué hace | Planes |
| --- | --- | --- |
| [**Solicitud HTTP**](#el-paso-solicitud-http) | La automatización **llama a tu sistema**: manda un lead a tu CRM, consulta el estado de un pedido, registra una respuesta. | Crecimiento, Escala y Enterprise (y la prueba gratis) |
| [**Webhook entrante**](#webhook-entrante) | **Tu sistema arranca la automatización**: un pedido entregado, un pago confirmado, un turno por vencer. | Escala y Enterprise |

```mermaid
flowchart LR
    A[Tu sistema] -- webhook entrante --> B[Automatización]
    B -- mensajes --> C[WhatsApp del cliente]
    B -- solicitud HTTP --> A
```

## El paso Solicitud HTTP

Agrega un paso **Solicitud HTTP** desde la paleta (grupo **Acciones**) y completa:

| Campo | Qué va |
| --- | --- |
| **Solicitud** | El método (`GET`, `POST`, `PUT`, `PATCH` o `DELETE`) y la URL. La URL admite variables: sus valores se codifican para URL, así que `{var.email}` no rompe la dirección. |
| **Encabezados** | Hasta 20, por ejemplo `Authorization: Bearer …` para autenticarte en tu sistema. |
| **Cuerpo** | El JSON que mandas (no en `GET`). Las variables se escapan para que el JSON no se rompa aunque el cliente escriba comillas o saltos de línea. Si no pones `Content-Type`, va `application/json`. |
| **Firmar la solicitud** | Agrega el encabezado `x-eltick-signature` para que tu sistema compruebe que el pedido viene de eltick. Mira [Verificar la firma](#verificar-la-firma). |
| **Guardar la respuesta en** | Una variable con el código y el cuerpo de la respuesta. |
| **Guardar partes de la respuesta** | Rutas dentro del JSON de la respuesta que se guardan cada una en su variable. |

<Frame>
  <img src="/images/automatizaciones/http.webp" alt="Panel de Solicitud HTTP con método POST, URL, cuerpo con variables y Firmar la solicitud marcado" />
</Frame>

Por ejemplo, para mandar un lead a tu CRM:

```json Cuerpo
{
  "nombre": "{var.nombre_lead}",
  "email": "{var.email}",
  "telefono": "{telefono}",
  "origen": "whatsapp"
}
```

### Salidas

| Salida | Cuándo |
| --- | --- |
| **Éxito** | Tu sistema respondió con un código `2xx`. |
| **Error** | Respondió con otro código, no respondió en **10 segundos**, la respuesta pesa más de **256 KB**, la URL no es válida o se usaron todas las solicitudes HTTP del mes. |

<Tip>
  Conecta siempre la salida **Error**. Por ejemplo, a un **Pasar a una persona**: si tu CRM está caído, el cliente igual recibe atención.
</Tip>

### Usar la respuesta

Si en **Guardar la respuesta en** pones `crm`, después puedes usar:

- `{var.crm.status}`: el código HTTP, por ejemplo `201`.
- `{var.crm.body}`: el cuerpo. Si es JSON, puedes entrar en él: `{var.crm.body.id}`, `{var.crm.body.items[0].precio}`.

Con **Guardar partes de la respuesta** le pones nombre propio a lo que te interesa. Por ejemplo, la ruta `data.estado` → variable `estado` te deja escribir _“Tu pedido está \{var.estado\}”_ o usar una **Condición** sobre la **Variable** `estado`.

Si la respuesta no es JSON, `body` queda como texto.

### Probar la solicitud

En el panel del paso, **Probar solicitud** manda la solicitud tal cual (con las variables sin completar) y te muestra el código y el cuerpo de la respuesta. Sirve para revisar la URL y los encabezados antes de publicar. Puedes probar hasta 20 veces por minuto.

### Qué no se puede

- Llamar a direcciones internas (`localhost`, `192.168.…`, `10.…` y similares). La URL tiene que ser pública.
- Esperar más de 10 segundos. Si tu sistema tiene que hacer algo largo, que responda enseguida con `202` y lo procese después.
- Reintentos automáticos: si falla, sale por **Error** una sola vez.

Cada solicitud cuenta para el cupo de **solicitudes HTTP por mes** de tu plan (5.000 en Crecimiento, 50.000 en Escala). Las de **Probar solicitud** y las de pruebas con un contacto no cuentan.

## Verificar la firma

Con **Firmar la solicitud**, cada pedido lleva el encabezado `x-eltick-signature` con el **mismo formato que los [webhooks de la API](/api/webhooks/firma)**:

```
x-eltick-signature: t=1790271068,v1=5f2b0c6e8a1d4b7e9c3f6a2d8b5e1c4f7a0d3b6e9c2f5a8d1b4e7c0f3a6d9b2e
```

- `t`: el momento de la firma, en segundos Unix.
- `v1`: el HMAC-SHA256, en hexadecimal, de `t`, un punto y el cuerpo crudo del pedido (vacío en un `GET`).

La diferencia es la clave: en lugar del secreto `whsec_…` de un webhook, se firma con el **token de la automatización** (`ats_…`). Lo generas en el disparador **Webhook entrante** con **Ver URL y token**, y se muestra **una sola vez**. Si la automatización todavía no tiene token, la solicitud sale **sin firma**.

<Warning>
  El token es uno por automatización y sirve para las dos cosas: firmar las solicitudes HTTP y autenticar el webhook entrante. Si generas uno nuevo, el anterior deja de valer para ambas.
</Warning>

<CodeGroup>

```javascript Node
import crypto from "node:crypto"
import express from "express"

const TOKEN = process.env.ELTICK_AUTOMATION_TOKEN // ats_…
const TOLERANCIA_SEGUNDOS = 5 * 60

function verificarFirma(cuerpoCrudo, encabezado, token) {
  const partes = Object.fromEntries(
    (encabezado ?? "").split(",").map((p) => {
      const i = p.indexOf("=")
      return [p.slice(0, i), p.slice(i + 1)]
    })
  )
  const t = Number(partes.t)
  if (!t || !partes.v1) return false
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCIA_SEGUNDOS) return false

  const esperada = crypto.createHmac("sha256", token).update(`${partes.t}.${cuerpoCrudo}`).digest("hex")
  const a = Buffer.from(partes.v1, "hex")
  const b = Buffer.from(esperada, "hex")
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

const app = express()

// El cuerpo crudo, sin parsear: si no, la firma no coincide
app.post("/leads", express.raw({ type: "*/*" }), (req, res) => {
  const cuerpo = req.body.toString("utf8")
  if (!verificarFirma(cuerpo, req.get("x-eltick-signature"), TOKEN)) {
    return res.status(401).json({ error: "Firma inválida" })
  }
  const lead = JSON.parse(cuerpo)
  // … guarda el lead en tu CRM
  res.status(201).json({ id: "L-1042", estado: "nuevo" })
})

app.listen(3000)
```

```python Python
import hashlib
import hmac
import json
import os
import time

from flask import Flask, request, jsonify

TOKEN = os.environ["ELTICK_AUTOMATION_TOKEN"]  # ats_…
TOLERANCIA_SEGUNDOS = 5 * 60

def verificar_firma(cuerpo_crudo: bytes, encabezado: str, token: str) -> bool:
    partes = dict(p.split("=", 1) for p in (encabezado or "").split(",") if "=" in p)
    t, v1 = partes.get("t"), partes.get("v1")
    if not t or not v1:
        return False
    if abs(time.time() - int(t)) > TOLERANCIA_SEGUNDOS:
        return False
    esperada = hmac.new(token.encode(), f"{t}.".encode() + cuerpo_crudo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada, v1)

app = Flask(__name__)

@app.post("/leads")
def leads():
    cuerpo = request.get_data()  # el cuerpo crudo
    if not verificar_firma(cuerpo, request.headers.get("X-Eltick-Signature", ""), TOKEN):
        return jsonify(error="Firma inválida"), 401
    lead = json.loads(cuerpo)
    # … guarda el lead en tu CRM
    return jsonify(id="L-1042", estado="nuevo"), 201
```

</CodeGroup>

Con esa respuesta, **Guardar partes de la respuesta** con la ruta `id` → variable `id_lead` te deja decirle al cliente _“Tu número de consulta es \{var.id_lead\}”_.

Si la firma no coincide, revisa los mismos puntos que en [los webhooks de la API](/api/webhooks/firma#si-la-firma-no-coincide): el cuerpo crudo, el token correcto y el reloj del servidor.

## Webhook entrante

Con el disparador **Webhook entrante**, tu sistema arranca la automatización para un teléfono: eltick busca el contacto (o lo crea), abre su conversación de WhatsApp y recorre el flujo con los datos que mandaste.

<Note>
  Los webhooks entrantes están en los planes **Escala** y **Enterprise**, y solo funcionan con **WhatsApp**.
</Note>

### Configurarlo

<Steps>
  <Step title="Agrega el disparador">
    En el editor, elige **Webhook entrante** en **Cuándo arranca**.
  </Step>
  <Step title="Toca Ver URL y token">
    Copia la URL y genera el token. El token (`ats_…`) se muestra **una sola vez**: guárdalo en las variables de entorno de tu sistema.

    <Frame>
      <img src="/images/automatizaciones/webhook.webp" alt="Diálogo Webhook entrante con la URL, el token y un ejemplo con curl" />
    </Frame>
  </Step>
  <Step title="Arma el flujo y publica">
    La automatización tiene que estar **Activa** para recibir llamadas.
  </Step>
</Steps>

### La llamada

```http
POST https://app.eltick.com/api/hooks/automations/{id}
x-eltick-token: ats_…
Content-Type: application/json
```

<ParamField body="phone" type="string" required>
  Teléfono del contacto, con código de país. Por ejemplo `+5491123456789`. Si no existe, se crea (y cuenta para el límite de contactos de tu plan).
</ParamField>
<ParamField body="name" type="string">
  Nombre del contacto, si lo sabes.
</ParamField>
<ParamField body="phoneNumberId" type="string">
  Desde cuál de tus números de WhatsApp escribir, si tienes varios. Si no lo mandas, se usa el primero que conectaste.
</ParamField>
<ParamField body="data" type="object">
  Lo que quieras usar en la automatización. Queda disponible como `{var.webhook.…}`: con `{"pedido": "1042"}` escribes `{var.webhook.pedido}`.
</ParamField>

<CodeGroup>

```bash curl
curl -X POST https://app.eltick.com/api/hooks/automations/01a0d4c2-7b1d-7e3f-9a40-5b6c7d8e9f01 \
  -H "x-eltick-token: $ELTICK_AUTOMATION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+5491123456789",
    "name": "Sofía Martínez",
    "data": { "pedido": "1042", "total": "$45.900", "retiro": "Av. Corrientes 1234" }
  }'
```

```javascript Node
const res = await fetch(
  "https://app.eltick.com/api/hooks/automations/01a0d4c2-7b1d-7e3f-9a40-5b6c7d8e9f01",
  {
    method: "POST",
    headers: {
      "x-eltick-token": process.env.ELTICK_AUTOMATION_TOKEN,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      phone: "+5491123456789",
      name: "Sofía Martínez",
      data: { pedido: "1042", total: "$45.900", retiro: "Av. Corrientes 1234" },
    }),
  }
)
if (!res.ok) console.error(res.status, (await res.json()).error)
else console.log(await res.json()) // { runId: "…" }
```

</CodeGroup>

```json 202
{ "runId": "01a0d4c3-1a2b-7c3d-8e4f-5a6b7c8d9e0f" }
```

`202` quiere decir que la ejecución arrancó. Lo que pase después (mensajes, respuestas, errores) lo ves en el [historial de ejecuciones](/guias/automatizaciones/probar#el-historial-de-ejecuciones), o en tu sistema con los webhooks [`automation.completed` y `automation.failed`](/api/webhooks/eventos#automation-completed).

<Tip>
  Si la herramienta que usas no deja poner encabezados, puedes mandar el token en la URL: `…/automations/{id}?token=ats_…`. Evítalo si puedes, porque las URLs suelen quedar guardadas en registros.
</Tip>

### Errores

| Código | Cuándo |
| --- | --- |
| `400` | Falta `phone` o no es válido: _“El teléfono “123” no es válido. Usá formato internacional…”_ |
| `401` | Falta el token o no es el de esta automatización. |
| `402` | Tu plan no incluye webhooks entrantes, la cuenta está pausada o llegaste al límite de contactos. |
| `409` | La automatización no está activa, el `phoneNumberId` no existe o esa conversación ya tiene una automatización en curso. |
| `429` | Superaste el tope por minuto o el cupo mensual de la API. |

Cada llamada cuenta como una **llamada a la API** y comparte sus topes (300 por minuto y 300.000 por mes en Escala). Mira [Límites de la API](/api/limites).

### Ten en cuenta la ventana de 24 horas

Una automatización que arranca desde tu sistema casi nunca tiene la ventana de 24 horas abierta: el cliente no te escribió hace poco. Por eso:

- **El primer mensaje tiene que ser una plantilla aprobada.** Si usas un texto, conecta su salida **Ventana cerrada** a una plantilla.
- **No empieces con una Pregunta**: fallaría con la ventana cerrada. Manda una plantilla con botones de respuesta rápida y arma otra automatización con un disparador de [palabra clave](/guias/automatizaciones/disparadores#palabra-clave) con el texto del botón.

La receta [Automatización desde tu sistema](/api/recetas/automatizacion-desde-tu-sistema) arma este caso completo.

## Con la API

Si ya usas la [API de eltick](/api/introduccion), puedes arrancar cualquier automatización activa con tu clave de API, sin token propio y sin necesidad de un disparador de webhook:

```bash
curl -X POST https://app.eltick.com/api/v1/automations/01a0d4c2-7b1d-7e3f-9a40-5b6c7d8e9f01/runs \
  -H "Authorization: Bearer $ELTICK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+5491123456789", "data": { "pedido": "1042" } }'
```

Usa el disparador **Inicio manual** de la automatización (o el primero, si no tiene). Mira la referencia de [`POST /automations/{id}/runs`](/api-reference/automatizaciones/iniciar).

| | Webhook entrante | API |
| --- | --- | --- |
| **Autenticación** | Token de la automatización (`x-eltick-token`) | Clave de API (`Authorization: Bearer etk_live_…`) |
| **Sirve para** | Una automatización | Todas las de la cuenta |
| **Respuesta** | `202` | `201` |
| **Ideal para** | Herramientas sin código, avisos de tu tienda | Tu propio backend |
