Notes

Receiving pings

A ping says only that something changed, and how far seq has come. It carries no data to trust, so a forged ping can do no worse than make you call seq. Check it all the same, and answer it fast.

Checking the signature

Each ping has an X-Signature header: the HMAC-SHA256 of its body, keyed with your whole API key, in base64. Compute it yourself and compare:

body := RawBody(request)  -- the bytes as received
want := Base64(HMAC-SHA256(key: apikey, message: body))
if not ConstantTimeEqual(want, request.headers["X-Signature"]) then
    return 403
end if
ping := JSONDecode(body)

Three mistakes to avoid:

  • Hashing a re-encoded body. Sign the bytes you received, before any JSON parsing. A framework that hands you the body parsed and serialized again can change spacing or key order, and the signature no longer matches.
  • Comparing with ==. An ordinary string comparison stops at the first difference, and its timing tells an attacker how much of a forged signature is right. Use your language's constant-time comparison, such as hash_equals in PHP, hmac.compare_digest in Python, or crypto.timingSafeEqual in Node.js.
  • Keying with the wrong string. The key is the whole API key, as you give it to Basic authentication before base64: the part before the colon, the colon, and the secret, as text. Not the secret alone, and not the base64 of the key. See Ping.

In v1, a shop has one ping URL, and its pings are signed with the shop's first API key. If you made more keys, as one for each server, check the signature with the first.

In v2, each API key has a ping URL of its own, and its pings are signed with that key. See Ping.

Answering

Answer at once, with 200 OK and the body ok. We don't act on the answer, but give each ping 7 seconds, from connecting to the first byte of it, and then hang up. Do the work after answering, or in another process: see Synchronizing with seq.

We don't retry a ping that fails, and we don't read your answer, so an error of yours is not resent. What makes up for it is that pings repeat: one comes whenever seq has new changes, and one 5 minutes after the last in any case, with the shop's "seq" as it is then. A ping whose "seq" you have already reached needs no work.

When pings stop coming

Pings are a prompt, not a promise. A ping can be lost, your server can be down when it comes, and a firewall, an expired certificate, or a redirect can turn them away without you noticing:

  • An https ping URL needs a certificate that a browser would accept, for the URL's host name. We check it, and don't ping a server whose certificate fails.
  • We don't follow redirects. A ping URL that answers with a 301, as when a site moves from http to https, gets nothing.

So:

  • Synchronize when your server starts, and on a timer too, as a safety net, if you depend on hearing of a payment within minutes. When there is nothing new, it costs one request.
  • Alert on a ping-free hour: the 5-minute pings mean that silence is a fault. Our status page says whether it is ours.