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

# Webhook Events

> Full catalogue of events Points delivers to your webhook endpoints

Every webhook event shares the same envelope. This page documents each event, when it fires, and any additional fields in the payload.

## Envelope

```json theme={null}
{
  "event": "<event-name>",
  "order": { /* OrderResource plus per-event extras */ },
  "timestamp": "2026-04-18T10:05:12Z"
}
```

The `order` object always contains at least:

| Field              | Type            | Example                                                                                          |
| ------------------ | --------------- | ------------------------------------------------------------------------------------------------ |
| `id`               | string (UUID)   | `550e8400-e29b-41d4-a716-446655440000`                                                           |
| `reference_number` | string \| null  | `REF-2024-001234`                                                                                |
| `order_number`     | string          | `ORD-2024-0001`                                                                                  |
| `type`             | string          | `earning` or `replacing`                                                                         |
| `total_price`      | number (SAR)    | `150.75`                                                                                         |
| `total_points`     | number          | `500`                                                                                            |
| `payment_status`   | string          | `fully_paid` or `partially_paid`                                                                 |
| `order_status`     | string          | `approved`, `authorized`, `captured`, `cancelled`, `fully_refunded`, `partially_refunded`, `new` |
| `metadata`         | object \| null  | `{ "source": "web" }`                                                                            |
| `created_at`       | ISO 8601 string | `2026-04-18T10:00:00Z`                                                                           |
| `updated_at`       | ISO 8601 string | `2026-04-18T10:05:12Z`                                                                           |

Headers on every delivery:

| Header             | Example                                       |
| ------------------ | --------------------------------------------- |
| `Content-Type`     | `application/json`                            |
| `X-Webhook-Secret` | `abc123def456...` (the secret you registered) |
| `X-Webhook-Event`  | Same as `event` in the body                   |

***

## `approved`

**Fires when:** an order is fully settled — a paid checkout completes, or an earning order is awarded.

**Use it to:** mark the order as confirmed in your system, send the customer a receipt, trigger fulfilment.

```json theme={null}
{
  "event": "approved",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_number": "REF-2024-001234",
    "order_number": "ORD-2024-0001",
    "type": "replacing",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "order_status": "approved",
    "metadata": { "source": "web" },
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:05:12Z"
  },
  "timestamp": "2026-04-18T10:05:12Z"
}
```

***

## `authorized`

**Fires when:** funds are authorised on a two-step (auth → capture) payment flow. No money has moved yet.

**Use it to:** reserve inventory, start fulfilment if your flow permits it before capture.

```json theme={null}
{
  "event": "authorized",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "type": "replacing",
    "order_status": "authorized",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:01:00Z"
  },
  "timestamp": "2026-04-18T10:01:00Z"
}
```

***

## `captured`

**Fires when:** previously-authorised funds are captured.

**Use it to:** finalise the order, release inventory holds, run post-sale automation.

```json theme={null}
{
  "event": "captured",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "order_status": "captured",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:10:00Z"
  },
  "timestamp": "2026-04-18T10:10:00Z"
}
```

***

## `cancelled`

**Fires when:** an order is cancelled via `POST /v1/orders/{uuid}/cancel`.

**Use it to:** release inventory, notify the customer, stop fulfilment.

```json theme={null}
{
  "event": "cancelled",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "order_status": "cancelled",
    "total_price": 150.75,
    "total_points": 0,
    "payment_status": "fully_paid",
    "reason": "Customer requested cancellation",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:30:00Z"
  },
  "timestamp": "2026-04-18T10:30:00Z"
}
```

The `reason` field, if supplied on the cancel request, is echoed in the payload.

***

## `completed`

**Fires when:** a replacing (checkout) order is marked complete via `POST /v1/orders/{uuid}/complete` — typically after the customer's payment is confirmed by the PSP.

**Use it to:** close out the order on your side if your flow distinguishes "approved" from "completed".

```json theme={null}
{
  "event": "completed",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "order_status": "approved",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T10:05:12Z"
  },
  "timestamp": "2026-04-18T10:05:12Z"
}
```

***

## `shipping_status_updated`

**Fires when:** you update a physical-goods order's shipping status via `POST /v1/orders/{uuid}/status`.

**Additional fields** (inside `order`):

| Field          | Type   | Example                                                                                                    |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------- |
| `status`       | string | One of `new`, `license_in_progress`, `ready_shipping`, `delivery_is_in_progress`, `delivered`, `cancelled` |
| `status_label` | string | Human-readable label (Arabic/English) shown on the customer's transaction history                          |

```json theme={null}
{
  "event": "shipping_status_updated",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "order_status": "approved",
    "status": "ready_shipping",
    "status_label": "Ready for shipping",
    "total_price": 150.75,
    "total_points": 500,
    "payment_status": "fully_paid",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T14:00:00Z"
  },
  "timestamp": "2026-04-18T14:00:00Z"
}
```

***

## `refunded`

**Fires when:** a refund is created through `POST /v1/orders/{uuid}/refund`.

**Use it to:** reverse fulfilment-side accounting, return value in your ERP, and mark the order as fully or partially refunded on your side.

**Additional fields** (inside `order`):

| Field           | Type   | Example                  |
| --------------- | ------ | ------------------------ |
| `refund_amount` | number | `25.00`                  |
| `reason`        | string | `One line item returned` |
| `type`          | string | `earning` or `replacing` |
| `refund_points` | number | `500`                    |
| `left_points`   | number | `1500`                   |
| `left_amount`   | number | `75.00`                  |

Not every refund payload includes every optional field. For example, `refund_points`, `left_points`, and `left_amount` are especially relevant for earning-order reversals.

```json theme={null}
{
  "event": "refunded",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "type": "earning",
    "order_status": "partially_refunded",
    "total_price": 150.75,
    "total_points": 3000,
    "payment_status": "fully_paid",
    "refund_amount": 25.00,
    "refund_points": 500,
    "left_points": 2500,
    "left_amount": 125.75,
    "reason": "One line item returned",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T15:00:00Z"
  },
  "timestamp": "2026-04-18T15:00:00Z"
}
```

***

## `auto_refunded`

**Fires when:** Points automatically reverses a replacing order internally, for example when a required transaction record is missing during backend reconciliation.

**Use it to:** treat the order as refund-class terminal state and stop assuming the checkout remained successful.

**Additional fields** (inside `order`):

| Field           | Type   | Example                           |
| --------------- | ------ | --------------------------------- |
| `refund_amount` | number | `150.75`                          |
| `refund_points` | number | `3000`                            |
| `reason`        | string | `auto_refund_missing_transaction` |
| `type`          | string | `replacing`                       |

```json theme={null}
{
  "event": "auto_refunded",
  "order": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_number": "ORD-2024-0001",
    "type": "replacing",
    "order_status": "fully_refunded",
    "total_price": 150.75,
    "total_points": 3000,
    "payment_status": "fully_paid",
    "refund_amount": 150.75,
    "refund_points": 3000,
    "reason": "auto_refund_missing_transaction",
    "created_at": "2026-04-18T10:00:00Z",
    "updated_at": "2026-04-18T16:00:00Z"
  },
  "timestamp": "2026-04-18T16:00:00Z"
}
```

***

## Testing events

The fastest way to trigger an event end-to-end is to issue the corresponding API call against a sandbox order:

| Event                     | API call                                         |
| ------------------------- | ------------------------------------------------ |
| `approved`                | `POST /v1/orders/earning` or complete a checkout |
| `authorized`              | `POST /v1/orders/{uuid}/authorize`               |
| `captured`                | `POST /v1/orders/{uuid}/capture`                 |
| `cancelled`               | `POST /v1/orders/{uuid}/cancel`                  |
| `completed`               | `POST /v1/orders/{uuid}/complete`                |
| `refunded`                | `POST /v1/orders/{uuid}/refund`                  |
| `auto_refunded`           | Internal event; not normally merchant-triggered  |
| `shipping_status_updated` | `POST /v1/orders/{uuid}/status`                  |

## Delivery semantics

* Points sends webhook deliveries over HTTPS
* each attempt uses a `10` second HTTP timeout
* failed deliveries may be retried up to `3` times
* order is not guaranteed across different events, so your consumer must be idempotent

## Next

<CardGroup cols={2}>
  <Card title="Overview" icon="bell" href="/webhooks/overview">
    How delivery works, secret verification, and the recommended consumer pattern.
  </Card>

  <Card title="Management" icon="gear" href="/webhooks/management">
    Register, update, rotate, and delete webhooks.
  </Card>
</CardGroup>
