anyshop docs homeStatusOpen the dashboard

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.

  • 6 event types
  • Standard Webhooks
  • Retries for 3 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.

typeWhendata
order.paidThe payment is confirmedThe order: number, total, buyer's email, items, what paid it
order.fulfilledEverything in the order is deliveredThe order, with each item's delivery state
order.refundedA refund, full or partialThe order, the refund's amount, the refunded total
dispute.createdA buyer disputed a paymentThe dispute: order, reason, amount, respond by
stock.lowA product's stock drops below your alert level, once a day at mostThe product, what is available, the level
license.activatedA device activated a licenseThe 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:

HeaderWhat it holds
webhook-idThe event's ID, the same on every retry: do the work once per ID
webhook-timestampWhen this attempt was signed, in Unix seconds
webhook-signaturev1, 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.

server.js
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
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
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.