# Generic RPC for orders

> Let the assistant read order status and run cancellations and returns on any store by answering signed RPC calls from ShopAssist.

Source: https://shopassist.coec.dk/docs/install/generic/order-rpc · Updated 2026-10-01

ShopAssist never stores your orders. When a verified shopper asks about an order, ShopAssist calls an HTTPS endpoint on your server, signed with a shared secret, and you answer with the order data. This is the Generic RPC.

## Configure the endpoint [#configure]

In **Settings → Connection → Order lookup**, enter your endpoint URL (HTTPS, reachable from the [egress IPs](/docs/security/egress-ips)). ShopAssist shows the **RPC secret** and, if the store has none yet, the **identity secret** once; store both in your server's secret store. ShopAssist then calls `health` immediately. Changing the URL's host issues a new secret.

## Request format [#request]

Every call is `POST` with a JSON body and the SA1 signature headers:

```text
X-ShopAssist-Store:     sto_…
X-ShopAssist-Key-Id:    k_…
X-ShopAssist-Timestamp: 1790000000
X-ShopAssist-Nonce:     3f1c… (32 lowercase hex)
X-ShopAssist-Signature: v1=<base64url HMAC-SHA256, no padding>
```

```json
{ "op": "customer.orders", "store": "sto_…", "params": { "identity_token": "eyJ…", "limit": 10 } }
```

The signature is `base64url(HMAC-SHA256(key = RPC secret bytes, message))`, where the message is these lines joined by `\n`: `SA1`, `b2p`, the store id, the timestamp, the nonce, `POST` and the lowercase hex SHA-256 of the raw body.

## Verify every call [#verify]

Your endpoint MUST:

1. Check the timestamp is within 300 seconds of your clock, the nonce was not seen in the last 600 seconds, the store id in the header equals `store` in the body, and compare the signature in constant time.
2. For customer operations, verify `identity_token` with the identity secret ([identity JWT](/docs/orders-and-identity/generic-identity-jwt)) and use its `sub` as the customer id. Only return that customer's orders.
3. For guest order operations, verify `order_access_token`: HS256 with the identity secret, `iss = "shopassist:sto_…"`, `aud = "generic:sto_…"`, `sub = "order:<your order id>"`, at most 120 seconds valid. Serve only that order. Write operations also require `lvl = "order_otp"` and `wrt = true`.
4. Answer mutations idempotently by `idempotency_key` (keep results at least 30 days).

Never return street addresses, phone numbers or payment details.

## Operations [#operations]

| Op                                                         | Required | Purpose                                                                                                 |
| ---------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `health`                                                   | Yes      | Protocol version (`plugin_protocol: 1`), supported `ops`, `server_time`, the guest factors you enforce  |
| `customer.orders`, `customer.order`, `orders.guest_lookup` | Together | Order list and detail for a signed-in customer; guest lookup by order number, email and a second factor |
| `orders.cancel.check`, `orders.cancel.execute`             | Optional | Customer cancellation                                                                                   |
| `returns.check`, `returns.create`                          | Optional | Return eligibility and return requests                                                                  |
| `orders.note`                                              | Optional | Private note on the order (for example "Confirmed by the shopper via ShopAssist (act\_…)")              |
| `inventory.levels`, `price.quote`                          | Optional | Live stock and live prices                                                                              |

Status values are normalized: `pending`, `processing`, `on_hold`, `shipped`, `partially_shipped`, `delivered`, `completed`, `cancelled`, `refunded`, `partially_refunded`, `failed`, plus your own `status_label` for shoppers.

## Responses [#responses]

```json
{ "ok": true, "data": { "orders": [ … ], "has_more": false } }
{ "ok": false, "error": { "code": "not_found", "message": "No such order", "details": null } }
```

Use HTTP `200` for both, `401` for signature failures (with `details.reason` and `server_time`), `429` with `Retry-After` when you throttle. Answer within 8 seconds. Error codes: `signature_invalid`, `unknown_op`, `invalid_params`, `identity_required`, `identity_invalid`, `not_found`, `not_eligible`, `unsupported`, `conflict`, `idempotency_conflict`, `rate_limited`, `maintenance`, `internal_error`.

## Health checks [#health]

ShopAssist runs `health` when you save the endpoint, every six hours and on demand (**Test connection**). After three failures in a row, order features switch off and the dashboard shows [`generic_rpc_failing`](/docs/troubleshooting/sync-issues#generic_rpc_failing) until the next success.

## Test vectors and samples [#samples]

Signing, token and envelope test vectors are published with the plugins; sample endpoints in Node.js, PHP and Python pass them. Start from the [webhook signature example](/docs/webhooks/verifying-signatures) for the HMAC pattern and the [identity JWT snippets](/docs/orders-and-identity/generic-identity-jwt) for token handling.
