---
name: ecomiq-webhooks
description: Recibe eventos de una tienda Ecomiq por webhook — registrar el endpoint, verificar la firma X-Ecomiq-Signature, responder a tiempo, procesar sin duplicar y recuperar entregas fallidas. Úsalo cuando haya que reaccionar a pedidos, productos, stock, clientes o carritos de Ecomiq (order.created, catalog.product.updated, catalog.stock.changed…).
---

# Ecomiq Webhooks

Ecomiq envía un `POST` firmado a tu URL cada vez que algo cambia en la tienda.

- Guía y modelos de cada evento: https://docs.ecomiq.pe/es/docs/guides/webhooks/events
- Índice para IA: https://docs.ecomiq.pe/llms.txt

## Registrar el endpoint

Con un token de Admin API (scope `api_admin`):

```bash
curl -X POST https://api.ecomiq.pe/api/v1/webhooks/endpoints \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pedidos al ERP",
    "url": "https://erp.example.com/webhooks/ecomiq",
    "eventTypes": ["order.created"],
    "secret": "<secreto-largo-y-aleatorio>"
  }'
```

- `url` y `eventTypes` son obligatorios. La URL debe ser HTTPS, pública
  (nunca una red privada o local), de hasta 2048 caracteres y única por tienda.
- Al crearlo, Ecomiq envía `webhook.endpoint.test`. El endpoint solo se guarda
  si esa prueba responde `2xx` antes de 15 segundos.
- Con `secret`, cada entrega va firmada. Úsalo siempre.

## Eventos

- Pedidos: `order.created`, `order.confirmed`, `order.in_progress`,
  `order.invoiced`, `order.ready_to_pick_up`, `order.in_transit`,
  `order.delivered`, `order.returned`, `order.cancelled`, `order.closed`,
  `order.invalidated`.
- Catálogo: `catalog.product.created|updated|deleted|status_changed`,
  `catalog.price.changed`, `catalog.stock.changed`,
  `catalog.inventory.batch_processed`, y `created|updated|deleted` de
  `catalog.brand`, `catalog.category` y `catalog.collection`.
- Clientes: `customer.created`.
- Carritos: `cart.created`, `cart.updated`, `cart.item_added`,
  `cart.item_removed`, `cart.completed`.
- Canales: `channel.created`, `channel.deleted`, `channel.sales.enabled`,
  `channel.listing.published`, `channel.listing.price_changed`.
- Operaciones: `operation.finished`, cuando una importación, exportación o
  acción masiva termina, falla o se cancela.

## Cabeceras de cada entrega

| Cabecera | Contenido |
| --- | --- |
| `X-Ecomiq-Event` | Tipo de evento, por ejemplo `order.created`. |
| `X-Ecomiq-Delivery` | UUID de la entrega. |
| `X-Ecomiq-Timestamp` | Momento del intento, en segundos Unix. |
| `X-Ecomiq-Signature` | `sha256=<hmac>`, solo si configuraste `secret`. |

## Verificar la firma

HMAC-SHA256 del cuerpo crudo con tu `secret`, en hexadecimal minúscula.
Verifica antes de leer el JSON y compara en tiempo constante.

```ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function isValidSignature(rawBody: string, header: string, secret: string) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const received = header.replace(/^sha256=/, "");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(received, "hex");

  return a.length === b.length && timingSafeEqual(a, b);
}
```

Si la firma no coincide, descarta la entrega y no la proceses.

## Responder y procesar

1. Responde `2xx` en menos de 15 segundos. Cualquier otro estado o un timeout
   se reintenta.
2. Guarda la entrega y procésala después (cola o tarea en segundo plano).
3. La entrega es al menos una vez: un evento puede llegar repetido con el mismo
   `X-Ecomiq-Delivery`. Procesa de forma idempotente y guarda ese ID al
   terminar.

El cuerpo usa campos en `camelCase` y los IDs son strings opacos. `order.created`
trae `orderId`, `orderNumber`, `state` y los totales, pero no las líneas: lee el
pedido completo con `GET https://api.ecomiq.pe/api/v1/orders/{orderId}`.

## Recuperar fallos

- Tras 10 fallos consecutivos, Ecomiq pausa el endpoint. Corrige el receptor y
  llama a `POST /v1/webhooks/endpoints/{id}/enable`.
- Lista las entregas fallidas con
  `GET /v1/webhooks/deliveries?status=failed&eventType=order.created`.
- Reenvía una con `POST /v1/webhooks/deliveries/{id}/retry`: conserva el cuerpo
  y crea un `X-Ecomiq-Delivery` nuevo.
