API versionv2 next

API v2 is not live yet: it launches in early 2027, and until then these pages are its spec. Switch to v1 (live)

Ping

A request we send you when a shop's sequence has grown, so that you call seq. It says which shop, and where the sequence ends now, and nothing else.

When we ping

You give an API key a ping URL in the dashboard, and we ping it about every shop that the key can act on: one shop for a shop's key, and every shop that the user or partner has for theirs. We ping:

  • As soon as a shop's sequence grows: a payment, an act, or a change to a subscriber, whoever made it. Changes that come together give one ping, with the latest "seq".
  • Every 5 minutes after the last ping, whether anything changed or not, with the "seq" as it is then.

A ping that fails is not sent again: the next change, or the next 5 minutes, brings another. Pings can arrive out of order, and more than once with the same "seq".

Request

POST {your ping URL}

We connect to your URL's host, on its port, or 443 for https and 80 for http. With https, your certificate must be valid for the host, as a browser would require: we don't ping a host whose certificate fails. We don't follow redirects.

Headers

HTTP header Description
Content-Type application/json; charset=UTF-8
Content-Length The length of the body.
X-Signature The body's signature, as base64: see Signature.
Connection close. Each ping has a connection of its own.

Body

Field Type Description
shopid integer The shop whose sequence has grown.
seq integer Where the shop's sequence ends now: the "seq" that seq returns when you have read all of it.
{"shopid":1234,"seq":164}

Signature

X-Signature is the base64, with padding, of the HMAC-SHA256 of the body as sent, with the API key as the HMAC's key: the whole key, as you give it to Basic authentication before base64, such as s1234#1:FhnIJVS2ZgRi2B35.... Check it before you trust the ping:

want := Base64(HMAC-SHA256(key: apikey, message: rawbody))
if not ConstantTimeEqual(want, headers["X-Signature"]) then
    return 403
end if
ping := JSONDecode(rawbody)
if ping.seq > LoadSeq() then
    WakeSynchronizer(ping.seq)
end if
return "ok"

Sign the bytes as received, before any parsing, and compare in constant time. Receiving pings has the mistakes to avoid.

A ping carries no data but the shop's ID and a number, so a forged one can do no worse than make you call seq. The signature lets you skip even that.

Response

Answer with 200 OK and the body ok, at once. We give a ping 7 seconds from connecting until the first byte of your answer, and then hang up, so synchronize after answering, or in another process: see Synchronizing with seq.

We don't act on your answer: a ping that you answer with an error, or not at all, is not sent again, as the next one comes anyway.