# Working with orders

Order and payment statuses, customer data, and keeping your data in sync between webhooks and the API.

## What an order contains

With the `orders:read` scope you receive the full order, except the customer's identity:

| Field | Example | Notes |
|---|---|---|
| `id` | `"ord_8Kd2"` | Stable string ID. Use it as the key in your database |
| `number` | `10482` | The number the merchant and the customer see |
| `status` | `"processing"` | Order status, as in the next table |
| `payment_status` | `"paid"` | `unpaid`, `paid` or `refunded` |
| `currency` | `"SAR"` | |
| `totals` | `{"subtotal":"180.00","discount":"0.00","shipping":"25.00","total":"205.00"}` | String amounts with two decimal places |
| `items` | | Product, name at the time of the order, kind, quantity and price |
| `order_url` | `"https://store.example/orders"` | The customer's orders page, the same link as the order email. It is not a link to this one order and does not sign the customer in. `null` when the store has no https domain |
| `created_at` `updated_at` `paid_at` `refunded_at` | | UTC |

An order never includes the payment method, gateway or card details. A full example is in [List orders](/api/orders-list).

## Order statuses

| `status` | Meaning |
|---|---|
| `pending` | Created and not yet in progress; usually not paid yet |
| `processing` | In progress: paid and awaiting delivery or shipping |
| `completed` | Completed and delivered |
| `cancelled` | Cancelled by the merchant, or the payment window expired |
| `returned` | Refunded in any way, or marked returned by the merchant |

`status` and `payment_status` answer different questions. A `pending` order is `unpaid`, a `processing` order is `paid`, and any refund makes the order `returned` with `payment_status: refunded`. To answer "has the customer paid?", read `payment_status`, not `status`.

## Customer data

With the `customers:read` scope, the order also includes:

```json
"customer": {
  "id": "c_2041",
  "name": "Sara Alotaibi",
  "phone": "+966512345678",
  "email": "sara@example.com"
},
"shipping_address": {
  "name": "Sara Alotaibi",
  "phone": "+966512345678",
  "country": "SA",
  "city": "Riyadh",
  "district": "Al Malqa",
  "street": "…",
  "postal_code": "13521",
  "short_address_code": "RAAA1234",
  "building_number": "2101",
  "secondary_number": "8899",
  "notes": null
}
```

- **The phone number is always in E.164 format.** Saudi numbers written as `05…`, `5…`, `9665…` or `009665…` arrive as `+9665…`. A number that cannot be validated arrives as `null`, never as raw text.
- `customer.id` is `null` for a guest order without an account.
- Every address field is a trimmed string or `null`; `shipping_address` itself is `null` when the order has no address.
- Without the scope, both keys are absent. Do not assume they exist.

> **Important:** Customer data belongs to the merchant, and your app processes it on the merchant's behalf. Store only what you need, and delete it on a [`customers.redact`](/guides/app-uninstall) request and after uninstall.

## Keeping in sync

Webhooks arrive within seconds but do not guarantee ordering, and the same webhook can arrive twice. The reliable pattern:

1. Subscribe to `order.created`, `order.paid`, `order.status.updated` and `order.refunded`.
2. When a webhook arrives, compare its `resource.version` with the last one you applied for that order: if it is lower, ignore it. Order events carry a strictly increasing version; the order object's own `resource_version` is always `0`.
3. Once an hour, fetch what changed with `updated_after` to catch any webhook you missed:

```bash
curl "https://larm.sa/stores/$STORE_ID/v1/orders?updated_after=2026-10-15T08:00:00Z&limit=100" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

List filters: `status`, `payment_status`, `created_after`, `created_before` and `updated_after`, plus `limit` and `cursor`. Any other filter returns 400 `INVALID_FILTER`. Results are sorted by creation time, newest first.

## Payments

With the `payments:read` scope you read payment outcomes: each payment has exactly `id`, `order_id`, `status` and `created_at`, where `status` is `succeeded` or `failed`. In-progress attempts are not returned, and payments carry no gateway, amount, currency or card details. For the order and its payment status, `orders:read` is enough; a later refund appears on the order as `payment_status: refunded` and the `order.refunded` webhook.

## Not available

- There is no endpoint to list customers. Customer data arrives only with their order or cart.
- Orders and their statuses cannot be modified through the API.
- Shipping and tracking data are not available yet.
