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

# Webhooks de pedidos

> Recibe un POST cuando un pedido llega a un estado al que te suscribiste.

Cuando un cliente hace un pedido en cualquier canal conectado, Fire spark hace `POST` del pedido a las URLs HTTPS que registres en el dashboard. Tu integración no consulta pedidos en bucle.

## Cómo llega un pedido a cocina

**1. El cliente realiza el pedido**

Storefront o un canal cierra el carrito. Fire spark inicia el fulfillment y
notifica los webhooks que coincidan.

**2. El pedido llega a un estado suscrito**

Suscríbete a `order.status.changed` y elige los estados que te importan
(`injecting`, `preparing`, etc.). Los eventos con nombre cubren anular,
cumplir, reembolsar y revertir.

**3. Crea el ticket en cocina**

Para el traspaso al POS o RMS, incluye `injecting` en esos estados. El
payload trae el pedido completo — crea el ticket tal cual.

**4. Reporta el siguiente estado desde tu POS**

Llama a [Actualizar estado del pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/status/patch)
con el siguiente estado de fulfillment. Fire spark puede enviar otro webhook.

> [!NOTE]
>
> El traspaso a cocina es `order.status.changed` con `data.status` `injecting`.
> No hay un evento de inyección aparte. Crea el ticket con el payload tal cual.

## Configura tu endpoint

Registra URLs HTTPS en **Ajustes → Webhooks** del dashboard (hasta 10 por entorno). Cada entorno tiene sus propios endpoints, y un pedido solo notifica los endpoints de su entorno. Elige **Webhook** o **Callback**.

* **Webhook** — se dispara cuando el trabajo de ese nodo está cerrado (persistido, liquidado, callbacks terminados). Si el nodo espera, la fila está en `WAITING` antes del POST. Si el siguiente nodo es automático, se notifica antes de entrar. Un terminal se notifica después de finalizar el run. Si el callback falla, no se envía el webhook de ese nodo. Elige uno o más eventos. Para `order.status.changed` también debes elegir los estados que deben disparar (`injecting`, `paid`, `preparing`, …).
* **Callback** — Fire spark espera tu `2xx` al entrar a un nodo que lista este endpoint. Los eventos no aplican. Puedes registrar la misma URL una vez de cada tipo.

| Requisito | Detalle |
| - | - |
| Protocolo | Solo HTTPS |
| Timeout | Por request, 1–300 segundos (default 5) |
| Reintentos | Tras el primer fallo, 0–10 (default 3). El delay es `min(1000 × 2^n, 30000)` ms — igual que [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/guides/query-retries) |
| Respuesta | Devuelve `2xx` dentro de `timeout`. En callbacks `INJECTING`, el JSON debe incluir `order_authorization_id` e `injection_order_id` |
| Entrega | Un POST por evento y estado que dispare (webhooks). Un POST esperado por intento (callbacks) |
| Idempotencia | Usa el `id` del sobre (`wh_…` o `cb_…`) para ignorar duplicados |
| Firma | Opcional. Si generas un secreto, las entregas incluyen `X-Firespark-Signature` |

> [!NOTE]
>
> Los webhooks creados antes del soporte de firma solo guardaban un hash. Rota
> (regenera) el secreto en el dashboard si necesitas verificar HMAC.

### Firma

Cuando hay secreto, Fire spark envía:

```
X-Firespark-Event: order.status.changed
X-Firespark-Delivery: wh_…
X-Firespark-Signature: sha256=<hex>
```

`<hex>` es HMAC-SHA256 del **cuerpo JSON crudo** con el secreto que se muestra una vez al crear o rotar (`whsec_…`).

## Tipos de evento

No existen `order.created` ni `order.updated`. Suscríbete a los eventos que coincidan con cómo opera tu cocina:

| Evento | Se dispara cuando | Uso típico |
| - | - | - |
| `order.status.changed` | El pedido llega a un estado que seleccionaste | **Crear el ticket** en `injecting`; sincronizar pasos posteriores |
| `order.voided` | El estado del pedido es `VOIDED` (cancelación sin pago, o [reapertura](https://firespark.cloud/docs/es/storefront-api/orders/\[id]/reopen/post)) | Anular el ticket. `data.status` es `voided`. |
| `order.fulfilled` | El pedido se cumple | Cerrar el ticket como cumplido |
| `order.refunded` | El pedido se reembolsa | Procesar el reembolso en el POS |
| `order.reversed` | Un pedido pagado se revirtió tras un callback fallido | Anular el ticket y revertir el cobro |
| `order.cancelled` | Se puede suscribir en el dashboard | El mapeo de estados no emite este evento. Las cancelaciones nuevas escriben `VOIDED` y disparan `order.voided`. |

> [!TIP]
>
> Un webhook suscrito a `order.status.changed` (estado `voided`) y a
> `order.voided` recibe **dos** POSTs. Distínguelos por `type` e `id`.
> `data.status` es el estado que disparó ese POST.

## Valores que vas a recibir

Estos son los conjuntos cerrados. El resto de `data.order` sigue [Obtener pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/get).

### `type` y headers

| Campo / header | Valores permitidos |
| - | - |
| `type` y `X-Firespark-Event` | `order.status.changed` \| `order.voided` \| `order.fulfilled` \| `order.refunded` \| `order.reversed` \| `order.cancelled` \| `order.callback` |
| `Content-Type` | `application/json` |
| `X-Firespark-Delivery` | Igual que el `id` del sobre (`wh_…`) |
| `X-Firespark-Signature` | `sha256=<hex>` si el webhook tiene secreto; si no, se omite |

### `data.status`, `current_states`, `next_states`

Siempre snake\_case en minúsculas. Los guiones pasan a guion bajo (`awaiting-pickup-time` → `awaiting_pickup_time`). `data.status` está siempre en `current_states`. `next_states` lista los **siguientes** estados del camino feliz a los que puedes avanzar — no los que se acaban de entrar. Los terminales (`fulfilled`, `voided`, `refunded`, `reversed`) se omiten; igual disparan eventos con nombre al entrar. Usa un valor de `next_states` al [actualizar el estado](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/status/patch).

**No** son un enum fijo — son los estados de fulfillment del comercio. Los caminos por defecto de Fire spark usan:

| Clave | Uso típico |
| - | - |
| `injecting` | Listo para el POS / cocina |
| `injected` | El POS / RMS aceptó el pedido |
| `idle` | Sin empezar |
| `paying` | Esperando el pago |
| `paid` | Pago capturado |
| `preparing` | Cocina empezó |
| `prepared` | Listo para retiro o courier |
| `awaiting_pickup_time` | Retiro programado aún no toca |
| `courier_heading_to_store` | Courier en camino a la tienda |
| `courier_at_store` | Courier en la tienda |
| `delivering` | En camino al cliente |
| `delivered` | Entregado al cliente |
| `fulfilled` | Fulfillment completo |
| `voided` | Estado del pedido `VOIDED`: cancelación sin pago, o el pedido origen de una reapertura |
| `cancelled` | Clave de fulfillment legada. Las cancelaciones nuevas usan `voided`. |
| `refunded` | Reembolsado (camino explícito) |
| `reversed` | Pedido pagado revertido tras un callback fallido |

El comercio puede agregar otras claves (mismo patrón snake\_case: `a-z`, `0-9`, guiones bajos). Suscríbete a las que ves en **Ajustes → Webhooks**.

Los eventos con nombre solo disparan con estas claves:

| `type` | `data.status` |
| - | - |
| `order.voided` | `voided` |
| `order.fulfilled` | `fulfilled` |
| `order.refunded` | `refunded` |
| `order.reversed` | `reversed` |

`delivered` no dispara `order.fulfilled`. Suscríbete a `order.status.changed` e incluye `delivered` si necesitas ese paso.

### Campos de estado en `data.order`

| Campo | Valores permitidos |
| - | - |
| `status` | `OPEN` \| `CLOSED` \| `COMPLETED` \| `VOIDED` \| `REVERSED` \| `REFUNDED` \| `CANCELLED` (filas legadas) |
| `payment_status` | `IDLE` \| `PENDING` \| `PAID` \| `FAILED` \| `CANCELLED` \| `REFUNDED` |
| `fulfillment_status` | Etiqueta del comercio, normalmente `UPPER_SNAKE` (`INJECTING`). No es el mismo casing que `data.status`. |
| `payment_intents[].status` | `DRAFT` \| `LOCKED` \| `COMPLETED` \| `ABANDONED` |
| `payment_intents[].tenders[].method` | `CASH` \| `CARD` \| `BANK_TRANSFER` \| `WALLET` \| `PIN_PAD` \| `FINANCING` \| `REDEMPTION` |
| `payment_intents[].tenders[].collect_at` | `DEVICE` \| `KIOSK` \| `COUNTER` \| `DOOR` \| `OTHER` |
| `payment_intents[].tenders[].status` | Igual que `payment_status` |
| `lines[].type` | `PRODUCT` \| `FEE` |

### Entrega

Webhooks: un intento por POST, timeout de 10 segundos. Cualquier `2xx` es éxito. Fire spark no reintenta solo.

Callbacks: el primer POST es inmediato. Tras cada fallo, espera `min(1000 × 2^failureCount, 30000)` ms y reintenta mientras `failureCount < retries` (default 3 → 4 POSTs máx.). Cada POST usa el `timeout` del endpoint (default 5s, máx. 300s). Un JSON inválido para un schema registrado (hoy `INJECTING`) cuenta como fallo. Si se agotan los reintentos, Fire spark entra a `VOIDED` (sin pago) o `REVERSED` (pagado) y no espera PATCH.

## Payload del webhook

Cada entrega usa el mismo sobre:

| Campo | Requerido | Tipo | Descripción |
| - | - | - | - |
| `id` | Sí | string | Id único de entrega. Usar para idempotencia |
| `type` | Sí | string | `order.status.changed`, `order.voided`, `order.fulfilled`, `order.refunded`, `order.reversed`, `order.cancelled` u `order.callback` |
| `created_at` | Sí | string | ISO 8601 de cuando se armó la entrega |
| `data` | Sí | object | Pedido, ids de alcance, estado que disparó y estados actuales |

### Objeto `data`

| Campo | Requerido | Tipo | Descripción |
| - | - | - | - |
| `merchant_id` | Sí | string | Comercio dueño del pedido |
| `order_id` | Sí | string | Id externo del pedido (el de `/orders/{id}`) |
| `fulfillment_id` | Sí | string | Opción de fulfillment externa (`delivery`, `pickup`, …) |
| `store_id` | Sí | string | Tienda externa |
| `channel_id` | Sí | string | Canal externo |
| `brand_id` | Sí | string \| null | Marca externa si el pedido tiene una |
| `customer_id` | Sí | string | Cliente externo |
| `status` | Sí | string | Estado de fulfillment que disparó este POST |
| `current_states` | Sí | string\[] | Todos los estados de fulfillment en los que está el pedido **ahora**. Siempre incluye `status`. El trabajo en paralelo puede sumar más |
| `next_states` | Sí | string\[] | Próximos estados del camino feliz a los que puedes hacer PATCH desde `current_states`. Vacío si solo quedan terminales |
| `order` | Sí | object | Pedido completo después de esta transición |

`data.status` es el disparador de esta entrega. `current_states` es el panorama ahora — el disparador más cualquier otro estado aún en curso (por ejemplo cocina y delivery a la vez). `next_states` es a lo que puedes hacer PATCH desde esos estados actuales. Si se entran dos estados suscritos juntos, recibes un POST por cada `status`, con el mismo `current_states` y `next_states`.

`order.fulfillment_status` es el último estado de fulfillment del pedido. `order.status` es el ciclo de vida (`OPEN`, `CLOSED`, …), no el disparador del webhook.

```json order.status.changed theme={null}
{
  "id": "wh_0193f0c2-7a1b-7c3d-9e4f-0123456789ab",
  "type": "order.status.changed",
  "created_at": "2026-06-17T14:32:00.000Z",
  "data": {
    "merchant_id": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
    "order_id": "ord-48291",
    "fulfillment_id": "delivery",
    "store_id": "downtown",
    "channel_id": "app",
    "brand_id": "0001",
    "customer_id": "cust-001",
    "status": "injecting",
    "current_states": ["injecting"],
    "next_states": ["injected"]
  }
}
```

`data.order` es el mismo objeto que [Obtener pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/get).

## Respuesta del callback (`INJECTING`)

Cuando un nodo mapeado a `INJECTING` invoca un callback, un `2xx` no basta. Devuelve JSON:

```json theme={null}
{
  "order_authorization_id": "auth-48291",
  "injection_order_id": "pos-9912"
}
```

Ambos campos siguen las mismas reglas de id externo (1–64 caracteres, `a-zA-Z0-9_-`). Fire spark los guarda en `order.metadata` para que salgan en [Obtener pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/get) y en webhooks posteriores. Un `2xx` con un cuerpo que no pase este schema cuenta como intento fallido.

## Manejar el traspaso a cocina

1. Suscríbete a `order.status.changed` e incluye `injecting`.
2. Lee `data.status`, `data.order_id`, los ids de alcance y el objeto `data.order`.
3. Mapea `fulfillment_id`, `store_id`, `channel_id`, `brand_id` e `id` de líneas a tu catálogo POS (los mismos ids de la [sincronización de menús](https://firespark.cloud/docs/es/integrations-api/guides/menu-sync.md)).
4. Crea el ticket con totales, cliente, pago y fulfillment **tal como vienen**.
5. Responde HTTP `2xx` de inmediato.
6. Cuando cocina avance, haz PATCH a [estado del pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/status/patch) con el siguiente estado de fulfillment.

> [!WARNING]
>
> Evita tickets duplicados: el mismo `order_id` puede entregarse más de una
> vez. Usa el `id` del sobre, o `order_id` más `data.status`.

## Obtener un pedido manualmente

Llama a [Obtener pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/get) con el `order_id` externo si necesitas reconciliar. Requiere el scope `orders:read`. Cada webhook ya incluye el `order` completo.

## Endpoints relacionados

> [card] [Obtener pedido](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/get)
>
> Recupera el pedido completo por id externo

> [card] [Actualizar estado](https://firespark.cloud/docs/es/integrations-api/orders/\[id]/status/patch)
>
> Reporta el siguiente estado de fulfillment desde tu POS


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