Order API
One endpoint. Your server tells us about each order placed through the app, and every status change after that.
Endpoint
POST https://easyvial.app/api/v1/orders?store={slug}Authentication
Every request is signed with your store's signing secret (dashboard → Store setup). Compute an HMAC-SHA256 of the exact raw request body with the secret as the key, hex-encode it, and send it in X-EasyVial-Signature. Examples in five languages →
| Header | Required | Value |
|---|---|---|
Content-Type | yes | application/json |
X-EasyVial-Signature | yes | Lowercase hex HMAC-SHA256 of the raw body |
X-EasyVial-Delivery-ID | no | Your own unique ID for the delivery — shown in our logs to help debugging |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Your order number. Must be unique within your store. Max 64 chars. |
order_key | string | recommended | Per-order secret — the same value you pass to EasyVial.trackOrder(). Links the order to the customer's phone. |
status | enum | yes | pending · processing · on-hold · completed · cancelled · refunded · failed |
total | string | number | yes | Order total in major units, e.g. "129.98". Include shipping and tax as charged. |
refunded_total | string | number | no | Total refunded so far, major units. Default 0. |
currency | string | no | ISO 4217 code. Default USD. |
utm_source | string | no | From the landing URL. easyvial marks the order as an app order on its own. |
utm_medium | string | no | app or push |
utm_campaign | string | no | Push campaign ID, when the order came from a push |
created_at | ISO 8601 | no | When the order was placed |
tracking_number | string | no | Add when the order ships. Customers get in-transit, out-for-delivery and delivered notifications automatically. |
carrier | string | no | usps · ups · fedex · dhl. Guessed from the number if omitted. |
tracking_url | string | no | Your own tracking page, if you have one. Otherwise we link to the carrier. |
Status meanings
| Status | Customer sees | Fee |
|---|---|---|
pending | Awaiting payment | — |
processing | “Your order is confirmed” notification | Charged |
on-hold | On hold | — |
completed | “Your order has shipped” notification | Charged |
cancelled | Cancelled | Reversed |
refunded | Refunded | Reversed (partial refunds: reduced by refunded_total) |
failed | Failed | — |
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | {"ok":true,"attributed":true} | Recorded. attributed says whether it counts as an app order. |
| 400 | {"error":"missing_id" | "bad_status" | "bad_json"} | Fix the request; don't retry as-is |
| 401 | {"error":"invalid_signature"} | Wrong secret, or the body changed after signing (re-serialising JSON is the usual cause) |
| 404 | {"error":"store_not_found"} | Wrong slug |
| 5xx | — | Retry with backoff (e.g. 1 min, 5 min, 30 min) |
Idempotency
Requests are idempotent per id: the latest request wins, and sending the same status twice changes nothing. Fees are recalculated from the current status and refunded total every time, so retries never double-charge.
Example
curl
BODY='{"id":"10423","order_key":"k_9f2c1a","status":"processing","total":"129.98","currency":"USD"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | sed 's/^.* //')
curl -X POST "https://easyvial.app/api/v1/orders?store=your-slug" \
-H "Content-Type: application/json" \
-H "X-EasyVial-Signature: $SIG" \
--data "$BODY"WooCommerce stores don't use this endpoint — the webhooks we register do the same job.