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

# Reciba y decida pedidos

> Construya el ciclo de pedidos: conecte un local, suscríbase a webhooks, verifique las entregas y gestione cada pedido con las llamadas accept, deny, ready y complete.

Este es el núcleo de una integración con Maple. Cuando un cliente hace un pedido en un local al que usted está conectado, Maple envía el pedido a su webhook; usted decide si lo prepara e informa su avance a medida que se mueve. **Nada de esto involucra pagos**: de eso se encarga Maple.

Una integración de pedidos funcional consta de dos partes:

| # | Qué construye | Esfuerzo habitual |
| - | - | - |
| 1 | Recibir pedidos: un endpoint de webhook con verificación de firma | \~1–2 días |
| 2 | Decidir pedidos: llamadas accept / deny / status hacia Maple | \~1–2 días |

Si aún no lo ha hecho, revise [Cómo funciona Maple](/es/developer-api/how-maple-works) para conocer el modelo de objetos, y siga el [Inicio rápido](/es/developer-api/quickstart) para obtener una clave y conectar un local de prueba.

## Paso 1 — Conecte un local

Los pedidos llegan a usted solo cuando su app es el receptor conectado de un local:

```bash theme={null}
curl -X POST $MAPLE_BASE/locations/{locationId}/connection \
  -H "Authorization: Bearer $MAPLE_KEY"
```

Las conexiones son exclusivas por entorno: si otra app ya tiene el local, recibirá un `409`. `GET` y `DELETE` en la misma ruta consultan y eliminan su conexión.

## Paso 2 — Suscríbase a los webhooks

Registre un endpoint HTTPS y los tipos de evento que desea. El catálogo completo está en `GET /v1/webhook_event_types`; para el ciclo de pedidos normalmente querrá estos:

```bash theme={null}
curl -X POST $MAPLE_BASE/webhook_subscriptions \
  -H "Authorization: Bearer $MAPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notification_url": "https://your-app.example.com/maple/webhooks",
    "event_types": ["order.notification", "order.validation_requested", "order.cancelled"]
  }'
```

La respuesta incluye un **secreto de firma de un solo uso** (`mwhsec_…`). Guárdelo; no se vuelve a mostrar. La URL debe ser HTTPS pública: se rechazan las direcciones privadas e internas.

<Note>
  Estos son los eventos de pedidos. El catálogo también incluye `order.created`, `order.paid`, `store.provisioned`,
  `store.deprovisioned`, `store.status.changed` y los eventos de sincronización de menú. Suscríbase solo a lo que vaya a
  procesar. Consulte la lista completa y los payloads en [Webhooks](/es/developer-api/concepts/webhooks).
</Note>

## Paso 3 — Verifique cada entrega

Cada entrega es un sobre al estilo de Stripe:

```json theme={null}
{
  "object": "event",
  "id": "evt_...",
  "type": "order.notification",
  "created": 1765432100,
  "data": { "order_id": "ord_...", "location_id": "str_...", "...": "the order content — see Step 4" }
}
```

Las entregas incluyen dos encabezados: `maple-webhook-id` (el id del evento) y `maple-webhook-signature` con el formato `t=<unix_seconds>,v1=<hex_hmac>`. La cadena firmada es `{timestamp}.{subscription_id}.{notification_url}.{raw_body}`. Verifíquela contra el cuerpo sin procesar, sin analizar:

```ts theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyMapleWebhook(params: {
  signatureHeader: string; // maple-webhook-signature
  subscriptionId: string; // your subscription id (dws_…)
  notificationUrl: string; // the exact URL you registered
  rawBody: string; // the unparsed request body
  signingSecret: string; // mwhsec_…
  toleranceSeconds?: number; // default 300
}): boolean {
  const parts = Object.fromEntries(params.signatureHeader.split(',').map((p) => p.split('=')));
  const timestamp = Number(parts.t);
  const provided = parts.v1 ?? '';

  // Reject stale deliveries to prevent replay.
  const skew = Math.abs(Math.floor(Date.now() / 1000) - timestamp);
  if (!Number.isFinite(timestamp) || skew > (params.toleranceSeconds ?? 300)) return false;

  const signed = `${timestamp}.${params.subscriptionId}.${params.notificationUrl}.${params.rawBody}`;
  const expected = createHmac('sha256', params.signingSecret).update(signed).digest('hex');
  return expected.length === provided.length && timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}
```

La verificación anterior rechaza marcas de tiempo vencidas (de más de \~5 minutos) para evitar repeticiones. Además, **elimine duplicados con el `id` del sobre**: la entrega es al menos una vez. El contrato de confiabilidad completo, con el calendario de reintentos y el libro de eventos, está en [Webhooks](/es/developer-api/concepts/webhooks).

### Ponerse al día tras una caída

Cuando su receptor no estuvo disponible (el software local estaba apagado o la red cayó), reconcilie así:

1. Llame a `GET /v1/orders?since=<timestamp>&limit=100` con su último punto de control exitoso (exclusivo, ISO 8601 UTC). Con `since`, los resultados vienen del más antiguo al más reciente y `has_more` indica que hay otra página.
2. Mientras `has_more` sea true, repita la solicitud con `starting_after` igual al último id de pedido de la página anterior. Continúe hasta vaciar el rezago y luego avance su punto de control.
3. Llame a `POST /v1/orders/{orderId}/resend` cuando necesite que Maple publique un `order.notification` nuevo para un pedido activo que no recibió su webhook. Requiere el alcance (scope) `webhooks:write`, crea una entrega nueva, firmada y con reintentos, y no cambia el estado del pedido. Los pedidos en estado terminal (`REJECTED`, `CUSTOMER_CANCELLED` o `STORE_CANCELLED`) no se pueden reenviar ni reactivar y devuelven `400`; reconcílielos con `GET /v1/orders?since=...` y `GET /v1/orders/{orderId}`.
4. Use el [registro de eventos](/es/developer-api/concepts/webhooks) en lugar de `resend` cuando importe conservar la identidad original del evento.

Esperar también funciona en una caída corta: el [calendario de reintentos](/es/developer-api/concepts/webhooks) vuelve a entregar un evento fallido durante unas 10 horas antes de considerarlo fallido por completo.

<Tip>
  Antes de tener tráfico real, `POST /v1/webhook_subscriptions/{id}/test` envía un evento `webhook.test` firmado para
  que pueda comprobar que su manejador verifica y responde correctamente.
</Tip>

## Paso 4 — Lea el pedido

El `data` del payload de `order.notification` es el **contenido del pedido**: sus IDs, cliente, artículos y totales, de modo que puede empezar a prepararlo directamente desde el webhook sin otra llamada. Sigue la forma de `GET /v1/orders/{orderId}`, pero omite el `status` y el `payment` en vivo. Consulte el recurso del pedido en cualquier momento para obtener el estado actual y autorizado, incluidos `status` y `payment`:

```json theme={null}
{
  "object": "order",
  "id": "ord_...",
  "livemode": false,
  "created": 1765432100,
  "status": "PENDING",
  "fulfillment_type": "delivery",
  "scheduled_for": null,
  "location_id": "str_...",
  "delivery_address": {
    "street": "123 Main Street",
    "unit": "Apt 4B",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "notes": "Leave at the side door"
  },
  "customer": { "name": "Alex", "phone": "+14155551234", "phone_last_four": "1234" },
  "line_items": [
    {
      "name": "Latte",
      "menu_entity_id": "itm-latte",
      "quantity": 1,
      "base_price": 450,
      "tax": 36,
      "modifiers": [{ "name": "Oat milk", "menu_entity_id": "mod-oat", "quantity": 1, "price": 75 }]
    }
  ],
  "totals": { "currency": "USD", "subtotal": 525, "tax": 42, "surcharge": 0, "tip": 0, "delivery_fee": 599, "delivery_tip": 0, "total": 1166 },
  "external_id": null,
  "payment": { "provider": "stripe", "status": "unpaid", "payment_link_url": "https://..." }
}
```

Algunos puntos que conviene tener presentes:

* **El dinero está en centavos de USD enteros.** `450` son \$4.50. Nunca lo analice como número decimal. Todos los montos en `totals` y en los artículos siguen esta convención.
* **Los totales vienen precalculados.** `totals.total` es el valor autorizado. No debe recalcular ningún precio.
* **Los artículos hacen referencia a su menú** mediante `menu_entity_id`, cuyo valor es el `externalId` que usted publicó para ese artículo. Publique primero un menú y estos coincidirán con su propio catálogo. (Los payloads de menú usan camelCase; los de pedidos y webhooks usan snake\_case; consulte [Convenciones](/es/developer-api/api-reference).)
* **Los modificadores son las opciones de primer nivel seleccionadas directamente.** Las selecciones de modificadores anidados más profundas no se expanden en el recurso del pedido en v1.
* **Los datos del cliente son intencionalmente mínimos.** Recibe el nombre y el número de teléfono del cliente (`phone` en formato E.164, más `phone_last_four` para mostrar). Los pedidos a domicilio también incluyen `delivery_address` con la calle, la unidad, la ciudad, el estado, el código postal y las instrucciones de entrega; es `null` en pedidos para recoger o cuando Maple no tiene una dirección guardada. Las seis claves de la dirección están presentes siempre que el bloque tenga datos.
* **Revise `scheduled_for` antes de enviar la comanda.** Es `null` en pedidos para ahora. Cuando tiene valor (ISO 8601, UTC), el cliente eligió esa hora de recogida o entrega; usted recibe el pedido de inmediato, así que programe la preparación para `scheduled_for`, no para el momento de recibirlo.

## Paso 5 — Decida e informe el avance

Responda mediante los endpoints de decisión a medida que avanza el pedido. Ninguno recibe cuerpo, salvo donde se indica:

| Llamada | Cuándo |
| - | - |
| `POST /v1/orders/{orderId}/accept` | Puede prepararlo |
| `POST /v1/orders/{orderId}/deny` | No puede; `{ "reason": "…" }` es opcional |
| `POST /v1/orders/{orderId}/ready` | Listo para recoger o para entregar al repartidor |
| `POST /v1/orders/{orderId}/complete` | Entregado |
| `POST /v1/orders/{orderId}/cancel` | Debe cancelar después de aceptar; `{ "reason": "…" }` es opcional |
| `POST /v1/orders/{orderId}/status` | Transición detallada (vea abajo) |

```bash theme={null}
curl -X POST $MAPLE_BASE/orders/{orderId}/accept \
  -H "Authorization: Bearer $MAPLE_KEY"
```

```json Response theme={null}
{ "object": "order_decision", "order_id": "ord_...", "decision": "accept", "status": "received" }
```

Si prefiere un solo endpoint, `POST /v1/orders/{orderId}/status` recibe una transición explícita:

```json theme={null}
{ "status": "ACCEPTED" }
```

Valores válidos: `ACCEPTED`, `READY`, `IN_DELIVERY`, `FULFILLED`, `REJECTED`, `STORE_CANCELLED`. Para saber qué significa cada estado y las transiciones permitidas entre ellos, consulte el [Ciclo de vida del pedido](/es/developer-api/concepts/order-lifecycle).

<Info>
  **Las llamadas de decisión son seguras ante repeticiones.** Repetir una devuelve `{ "status": "received" }` sin efectos
  secundarios dobles, por lo que reintentar ante una falla de red siempre es seguro. Consulte [Idempotencia y
  seguridad ante repeticiones](/es/developer-api/concepts/idempotency).
</Info>

## Opcional — Prevalide los pedidos

La prevalidación es **opcional y se activa suscribiéndose a `order.validation_requested`**:

* **Si no se suscribe**, no hay paso de validación. Maple envía `order.notification` directamente y su accept/deny es la única barrera.
* **Si se suscribe**, Maple le pide confirmar que cada pedido se puede preparar (disponibilidad de artículos, viabilidad de precios, posibilidad de inyección en el POS) *antes* de enviar la notificación, y espera su respuesta. Por lo tanto, activarla es un compromiso: un pedido que no valide a tiempo se rechaza (vea abajo).

Ese es todo el mecanismo: la suscripción es el interruptor.

Una vez suscrito, responda a cada `order.validation_requested` con un resultado:

```bash theme={null}
curl -X POST $MAPLE_BASE/orders/{orderId}/validation_result \
  -H "Authorization: Bearer $MAPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "valid", "partner_reference": "pos_quote_123" }'
```

Para bloquear el pedido:

```json theme={null}
{
  "status": "invalid",
  "reason": { "code": "ITEM_AVAILABILITY", "explanation": "86'd", "item_external_ids": ["itm-latte"] }
}
```

Un resultado `valid` permite que el pedido pase a la notificación; `invalid` lo bloquea antes de que se cobre al cliente. Tiene unos **5 minutos** para responder antes de que la solicitud venza (el recurso incluye `expires_at`, a \~300 segundos). Si vence, Maple solicita la validación **una vez más**; si esa segunda solicitud tampoco se responde, el pedido se rechaza sin notificación, así que suscríbase solo cuando su manejador responda de forma confiable y a tiempo. Es la misma verificación de disponibilidad que haría al aceptar, solo que antes, para que los clientes no paguen por algo que usted no puede preparar.

## Cómo se ve "terminado"

| Usted construye | Usted obtiene |
| - | - |
| Un manejador de webhook que verifica, elimina duplicados y enruta | Pedidos en tiempo real con totales precalculados |
| Llamadas accept / deny / ready / complete / status | Los clientes ven el avance del pedido en vivo |
| *(Opcional)* respuestas de validación | Pedidos inválidos bloqueados antes de cobrar al cliente |

Un webhook y un puñado de llamadas de decisión. Los pagos y los precios quedan de nuestro lado, y el equipo de Maple está disponible durante toda su integración.

## Siguiente

<CardGroup cols={2}>
  <Card title="Publique un menú" icon="book-open" href="/es/developer-api/guides/publish-menu">
    Haga que `menu_entity_id` en cada artículo corresponda a su propio catálogo.
  </Card>

  <Card title="Webhooks a fondo" icon="bell" href="/es/developer-api/concepts/webhooks">
    Garantías de entrega, reintentos, el registro de eventos y la repetición.
  </Card>
</CardGroup>


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