API versionv1 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

A shop has one ping URL, which you set in the dashboard. We ping it:

  • 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". A shop without a ping URL gets no pings, and has to call seq on a timer.

Request

POST {your ping URL}

We connect to your URL's host, on its port, or 443 for https and 80 for http, and send the ping as HTTP/1.0. 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. Use https: http works, but sends the ping, and its signature, in the clear.

Headers

HTTP header Description
Host Your URL's host, and its port if the URL has one.
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 shop's API key as the HMAC's key: the whole key, as you give it to Basic authentication before base64, with the shop ID in front, such as 1234: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 read your answer beyond its first byte, and don't act on it: a ping that you answer with an error, or not at all, is not sent again, as the next one comes anyway.