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

# Track Order Fulfillment

> Subscribe to order webhooks and respond to fulfillment lifecycle events. Covers all four event types and delivery status transitions.

The Track Order Fulfillment pattern subscribes to Snappy's order webhooks and keeps your system — and the recipient — up to date as the order moves from confirmed to delivered. This is push-based: Snappy calls your endpoint on each status change.

## The four order events

| Event                           | When it fires                                                   |
| :------------------------------ | :-------------------------------------------------------------- |
| `order-status-changed`          | Order status changes (e.g., `active` → `fulfilled`)             |
| `order-delivery-status-changed` | Delivery status transitions (e.g., `processing` → `in_transit`) |
| `order-canceled`                | Order is canceled before fulfillment                            |
| `order-out-of-stock`            | A line item is out of stock and cannot be fulfilled             |

Subscribe to these in your [Webhook configuration](/pages/overview-and-setup).

## Delivery status values

<Warning>
  V3 delivery statuses use **snake\_case**, not camelCase. Use `in_transit` and `out_for_delivery` — not `inTransit` / `outForDelivery`.
</Warning>

| Status             | Meaning                                   |
| :----------------- | :---------------------------------------- |
| `confirmed`        | Order received by the vendor              |
| `processing`       | Being prepared for shipment               |
| `in_transit`       | Shipped and in transit — tracking is live |
| `out_for_delivery` | Out for delivery today                    |
| `delivered`        | Arrived at the recipient's address        |

## Metadata roundtrips

`metadata` (the arbitrary object you pass at order creation) roundtrips on `order-status-changed` and `order-delivery-status-changed` events, but **not** on `order-canceled` or `order-out-of-stock`. If you need to correlate those events with your internal records, store the mapping in your own database by `orderId`.

## Webhook handler

```javascript JavaScript theme={null}
// Express handler
app.post("/webhooks/snappy", express.json(), async (req, res) => {
  // Acknowledge immediately — process async
  res.sendStatus(200);

  const { webhookData, eventData } = req.body;

  switch (webhookData.eventType) {
    case "order-delivery-status-changed": {
      await db.orders.updateDeliveryStatus(eventData.orderId, eventData.status);
      if (eventData.status === "in_transit") {
        await notifyRecipient(eventData.orderId, {
          message: "Your order is on its way!",
          trackingLink: eventData.trackingLink,
        });
      }
      if (eventData.status === "delivered") {
        await notifyRecipient(eventData.orderId, { message: "Your order has arrived!" });
      }
      break;
    }
    case "order-status-changed": {
      await db.orders.updateStatus(eventData.orderId, eventData.status, eventData.metadata);
      break;
    }
    case "order-canceled": {
      await db.orders.markCanceled(eventData.orderId);
      await notifyRecipient(eventData.orderId, { message: "Your order was canceled." });
      break;
    }
    case "order-out-of-stock": {
      await db.orders.markOutOfStock(eventData.orderId);
      await alertOpsTeam(eventData.orderId, "Out of stock");
      break;
    }
  }
});
```

```python Python theme={null}
# Flask handler
@app.post("/webhooks/snappy")
def snappy_webhook():
    # Acknowledge immediately
    body = request.get_json()
    process_webhook.delay(body)  # push to background task queue
    return "", 200

def handle_webhook(body):
    webhook_data = body["webhookData"]
    event_data = body["eventData"]
    event_type = webhook_data["eventType"]

    if event_type == "order-delivery-status-changed":
        db.update_delivery_status(event_data["orderId"], event_data["status"])
        if event_data["status"] == "in_transit":
            notify_recipient(event_data["orderId"], tracking_link=event_data.get("trackingLink"))
        elif event_data["status"] == "delivered":
            notify_recipient(event_data["orderId"], message="Your order has arrived!")

    elif event_type == "order-status-changed":
        db.update_order_status(event_data["orderId"], event_data["status"], event_data.get("metadata"))

    elif event_type == "order-canceled":
        db.mark_canceled(event_data["orderId"])

    elif event_type == "order-out-of-stock":
        alert_ops_team(event_data["orderId"])
```

## On-demand order fetch

You can also pull order state at any time without relying on webhooks:

```text theme={null}
GET /v3/orders/{orderId}
```

This is useful for reconciliation, initial page loads, or as a fallback if your webhook handler missed an event.

```javascript JavaScript theme={null}
async function getOrder(orderId) {
  const res = await fetch(`${SNAPPY}/v3/orders/${orderId}`, { headers });
  if (!res.ok) throw new Error(`GET order failed: ${res.status}`);
  return res.json(); // full order object
}
```

## Reliability tips

* **Acknowledge fast, process async.** Return `200` immediately and do the work in a background job. Slow handlers risk timeouts and missed retries from Snappy.
* **Handle duplicate deliveries.** Webhooks may be delivered more than once. Make your handlers idempotent — updating a DB record to the same status is a no-op.
* **Use `GET /v3/orders/{orderId}` for reconciliation.** On startup or after downtime, poll recent orders to catch any events you missed.

## Related

* [Place Orders](/patterns/place-orders) — creating the order that these webhooks track
* [Auto-claim Gifts as Fallback](/patterns/auto-claim-gifts-as-fallback) — a pattern that also relies on webhooks for coordination
