GuidesReact to orders
React to orders
on your servers.
anyshop tells your servers when something happens in your store: a payment confirmed, an order delivered, a refund, a dispute, low stock, a license activated. Every event is signed, sent again until your server takes it, and kept for 30 days.
Events
Each event is one JSON body, { "type", "timestamp", "data" }, where data is the object it is about. Subscribe an endpoint to a list of types, or to "all" for these and any added later.
| type | When | data |
|---|---|---|
order.paid | The payment is confirmed | The order: number, total, buyer's email, items, what paid it |
order.fulfilled | Everything in the order is delivered | The order, with each item's delivery state |
order.refunded | A refund, full or partial | The order, the refund's amount, the refunded total |
dispute.created | A buyer disputed a payment | The dispute: order, reason, amount, respond by |
stock.low | A product's stock drops below your alert level, once a day at most | The product, what is available, the level |
license.activated | A device activated a license | The license, its product, devices used and allowed, the activation |
Amounts are integers in minor units, and times ISO 8601 in UTC, as everywhere in the API.
Add an endpointtest key
The address must be public https: anyshop never follows redirects or calls private networks, and waits 15 seconds for an answer. The secret in the answer is shown once; keep it with your server's other secrets. You can also add endpoints in the dashboard, under Developers, Webhooks.
curl https://api.anyshop.io/v1/webhook_endpoints \
-H "Authorization: Bearer $ANYSHOP_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.pixelforge-supply.com/anyshop",
"events": ["order.paid", "order.refunded", "dispute.created"]
}'Verify signatures
Deliveries follow Standard Webhooks, so its libraries for most languages verify them. Every request carries three headers:
| Header | What it holds |
|---|---|
webhook-id | The event's ID, the same on every retry: do the work once per ID |
webhook-timestamp | When this attempt was signed, in Unix seconds |
webhook-signature | v1, and the signature; during a secret rotation, two of them separated by a space |
The signed content is the ID, the timestamp and the raw body joined by dots. Verify against the body exactly as it arrived, before any JSON parsing, and refuse timestamps more than 5 minutes away from your clock: the libraries do both.
import express from "express";
import { Webhook } from "standardwebhooks";
const webhook = new Webhook(process.env.ANYSHOP_WEBHOOK_SECRET); // whsec_...
const app = express();
app.post("/anyshop", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = webhook.verify(req.body, req.headers);
} catch {
return res.sendStatus(400);
}
res.sendStatus(204); // answer first, then work
await queue.add(req.headers["webhook-id"], event);
});Answer quickly
Any 2xx within 15 seconds marks the delivery done; anything else, a timeout or no connection is a failure, tried again later. Put the event on a queue and answer, then do the work. Because a retry can arrive after your server did the work but before its answer got through, handle each webhook-id once.
Retries
A failed delivery is tried again 5 seconds later, then after 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 10 hours, 24 hours and 24 hours: 10 attempts over about 3 days, each wait up to 20% shorter or longer at random so a recovering server is not hit all at once. A Retry-After header in your answer is honored, up to 24 hours. Every attempt has the same webhook-id and a fresh timestamp and signature.
- An endpoint failing for 24 hours: you get one email.
- Failing for 5 days: the endpoint is disabled, and you get another.
- Answering
410 Gone: the endpoint is disabled at once, as asked.
Pause an endpoint while you deploy with PATCH /v1/webhook_endpoints/{id} and "status": "paused": events wait for it, and go out when you set it back to enabled. The same call enables a disabled endpoint again.
Replays
Nothing is lost while your server is down. In the dashboard, under Developers, Webhooks, each endpoint has its delivery log and three ways back:
- Replay now sends one delivery again, whatever its state.
- Recover since starts the schedule again for every failed delivery since a time you choose.
- Replay missing sends the events of the endpoint's types that it never got, such as those from while it was disabled or before you added a type to it.
Your server can also catch up on its own: GET /v1/events lists everything that happened in the key's mode in the last 30 days, newest first, whether or not an endpoint received it, and GET /v1/events/{id} fetches one by its webhook-id.
curl "https://api.anyshop.io/v1/events?type=order.paid&limit=100" \
-H "Authorization: Bearer $ANYSHOP_TEST_KEY"Rotate the secret
POST /v1/webhook_endpoints/{id}/rotate_secret returns a new secret, once. For the next 24 hours every delivery is signed with both, so deploy the new secret whenever it suits you within the day; a server with either one keeps accepting deliveries.
curl -X POST \
https://api.anyshop.io/v1/webhook_endpoints/whe_01m3rs9d2f4k6m8p0r2t4v6x8z/rotate_secret \
-H "Authorization: Bearer $ANYSHOP_TEST_KEY"Ed25519 signatures
Create an endpoint with "signing": "ed25519" when the servers that verify should not hold a secret that can also sign. anyshop keeps the private key; your server gets the public key, whpk_..., and signatures start with v1a,. Standard Webhooks libraries that support asymmetric signatures verify them the same way.