> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.rushorder.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.rushorder.com/_mcp/server.

# Receiving Deliveries

## The request

Each delivery is an HTTPS `POST` with a JSON body.

| Header              | Value                                                              |
| ------------------- | ------------------------------------------------------------------ |
| `Content-Type`      | `application/json; charset=utf-8`                                  |
| `User-Agent`        | `RushOrder-Webhooks/1.0`                                           |
| `webhook-id`        | The delivery ID, a UUID, identical on every retry of the delivery. |
| `webhook-timestamp` | When this attempt was sent, in Unix seconds.                       |
| `webhook-signature` | Space-separated signatures, each `v1,<Base64 value>`.              |

| Body field         | Description                                                                                                           |
| ------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `type`             | The event type.                                                                                                       |
| `timestamp`        | When the event happened, ISO 8601 in UTC (`2026-09-21T23:31:00+00:00`).                                               |
| `rushOrderMomCode` | The Rush Order client code of the account, such as `ABC`.                                                             |
| `data`             | The event's fields, described in the [event reference](/webhooks/event-reference). Fields without a value are `null`. |

The [event reference](/webhooks/event-reference) shows a complete body for each event type. Events can
gain new fields and values over time, so ignore fields you don't recognize.

## Verifying a delivery

Verify every delivery before acting on it:

1. The signed content is `{webhook-id}.{webhook-timestamp}.{body}`: the two header values
   and the raw body, joined by periods.
2. The key is the part of the secret after `whsec_`, Base64-decoded.
3. The expected signature is the HMAC-SHA256 of the signed content with that key,
   Base64-encoded.
4. The delivery is authentic when the expected signature equals the value after `v1,` in
   any of the header's signatures.

> **Verify the raw body**
>
> The signature covers the body exactly as received. Verify the raw bytes before
> parsing them; JSON that is parsed and re-serialized will not match.

Reject a `webhook-timestamp` more than five minutes from the current time, to stop old
deliveries from being replayed.

The scheme follows the [Standard Webhooks](https://www.standardwebhooks.com/)
specification, so the
[Standard Webhooks libraries](https://github.com/standard-webhooks/standard-webhooks)
verify deliveries in many languages. The example below needs only the Python standard
library (3.6 or later).

```python maxLines=0
import base64
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 5 * 60


def signature_matches(secret: str, message_id: str, timestamp: str,
                      signature_header: str, body: bytes) -> bool:
    key = base64.b64decode(secret[len('whsec_'):])
    signed_content = f'{message_id}.{timestamp}.'.encode() + body
    expected = base64.b64encode(
        hmac.new(key, signed_content, hashlib.sha256).digest())
    for signature in signature_header.split(' '):
        version, _, value = signature.partition(',')
        if version == 'v1' and hmac.compare_digest(value.encode(), expected):
            return True
    return False


def verify_webhook(secret: str, headers: dict, body: bytes) -> bool:
    """headers: the request's headers, keyed by lower-case name.
    body: the raw request body, exactly as received."""
    timestamp = headers['webhook-timestamp']
    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    return signature_matches(secret, headers['webhook-id'], timestamp,
                             headers['webhook-signature'], body)
```

#### Test values

These values check an implementation of the signature step on its own (the timestamp
is too old to pass the five-minute check):

| Input               | Value                                             |
| ------------------- | ------------------------------------------------- |
| Secret              | `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`          |
| `webhook-id`        | `msg_p5jXN8AQM9LWM0D4loKWxJek`                    |
| `webhook-timestamp` | `1614265330`                                      |
| Body                | `{"test": 2432232314}`                            |
| Expected signature  | `v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=` |

## Responses and retries

| Your response                                                                      | Result                                                                                                                             |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `2xx` within 30 seconds                                                            | Delivered.                                                                                                                         |
| `408`, `429`, `5xx`, no response within 30 seconds, or a connection or TLS failure | Retried: up to six attempts in all, immediately and then 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours after each failure. |
| `3xx` or any other `4xx`                                                           | Not retried.                                                                                                                       |

Each retry uses the subscription's current URL and secret. Pausing or deleting a
subscription cancels its pending retries.

> **Respond quickly**
>
> Return a `2xx` as soon as the delivery is verified and stored, and do slower work
> afterward, so long processing doesn't run past the 30-second limit.

## Duplicates and ordering

Delivery is at least once. A repeated delivery carries the same `webhook-id`, so use it to
skip deliveries you have already processed.

A new event for the same order or package has a new `webhook-id`: `order.shipped` again
when a package gains a tracking number, and `order.whready` again when an order re-enters
the warehouse system under a new order number.

Deliveries can arrive out of order. `timestamp` gives the time each event happened.