Skip to content

Recovering from errors

A terminal works on a shop's network, which drops. This guide says what the app does when a call fails, and when it cannot tell whether a call took effect.

Every refusal is the usual error envelope, described in Errors: a type that always comes with the same HTTP status, a stable code to branch on, a message for your logs that may be reworded, the param at fault when there is one, and a request_id. Log the request_id with the failure, and quote it when you contact support.

Most refusals are the terminal's to act on, not to retry:

StatuscodeWhat it meansWhat to do
400invalid_parameter, and the other validation_failure codesThe request is not valid; param names what.Fix the request. Sent again unchanged, it is refused again.
401terminal_credential_refusedThe terminal's certificate does not count.Stop. See A refused terminal.
404resource_missingThis terminal has no session with that id.See A lost session.
409session_already_openThe terminal already has a live session, or the client_session_token was first sent with an earlier certificate.Resume or close the live session; for a token from before a renewal, resume that session if it is live, or open with a new token. See A lost session.
409idempotency_key_reusedA cash payment's idempotency_key recorded a different sale.Use a new key for a new sale.
422session_interruptedThe session missed its heartbeats.Resume it.
422session_expired, session_closedThe session is over.Open a new one. See An expired session.
422other policy_rejection codesThe terminal may not do this now, for the reason in code.Show the cashier; retrying will not help.
429rate_limitedPairing only: too many requests from this address.Wait for Retry-After seconds.
503service_unavailableThe platform could not answer, or failed part-way. Whether the call took effect is not known.Retry the same request, with the same client_session_token, idempotency_key or CSR: a call that was done is answered, not repeated. Never make a new token or key for the retry.

When a call times out, or the connection drops before the answer arrives, the terminal cannot tell whether the call took effect. Send the same request again: every terminal call is safe to repeat, each for its own reason, so a retry never does a thing twice.

CallWhy sending it again is safe
PairThe same code and the same key return the same certificate, while the code has not expired. Do not make a new key for the retry.
Renew a certificateThe old certificate and the same key return the same new certificate, until the new one is first used.
Open a sessionThe same client_session_token answers the same session, as it stands now.
Send a heartbeatIt moves last_heartbeat_at and changes nothing else.
Resume a sessionA session already active is answered as it is.
Close a sessionA session already closed is answered unchanged.
Record a cash paymentThe same idempotency_key with the same terms answers the same payment with 200, and records nothing twice.
Retrieve the calling terminalIt only reads.

What makes a retry safe is in the request itself: the code and key, the certificate, the client_session_token or the idempotency_key. Terminal calls take no Idempotency-Key header. So keep each of these until its call has answered, and make a new one only for a new session or a new sale.

  • Retry a timeout, a dropped connection and 503 service_unavailable, with the same request, after a short wait, and wait longer after each failure. A 503 does not say the call was not done, any more than a timeout does: the same token, key or CSR makes the retry answer a call that was done rather than repeat it.
  • Do not retry unchanged a 400, 401, 404, 409 or 422: the same request gets the same answer. Do what the tables say.
  • Keep heartbeating while other calls fail. A missed heartbeat is not an error: a session is interrupted once 3 heartbeat intervals, 90 seconds by default, have passed after its last heartbeat, so heartbeat on the interval, not at the edge of it. An interrupted session can still be resumed until 5 minutes after the last heartbeat that reached the platform. Both are set by the platform. If a heartbeat is refused with session_interrupted, resume the session and carry on.

401 terminal_credential_refused on any call means the terminal's certificate does not count, so every call will be refused the same way. Stop calling, and tell the cashier the terminal needs the operations team. What was wrong is not said, on purpose. It is one of these:

  • The credential was revoked, as for a lost or stolen terminal. It is refused on its very next call, whatever the certificate's expiry says.
  • The certificate expired: the terminal did not renew it in time. A terminal off for longer than its certificate's lifetime, 7 days by default and set by the platform, cannot renew.
  • The terminal was paired again, or moved to another store or merchant.
  • The terminal was suspended or retired.

Except for a suspension or a retirement, the terminal is paired again, with a new Pairing Code and a new key: see When a terminal is refused. A suspended terminal's certificate is honoured again once it is reinstated, unless it has expired meanwhile.

To stay clear of an expired certificate, renew it once a day.

422 session_expired means the session was not resumed by its recovery_deadline, or reached its expires_at, 12 hours after it opened by default. 422 session_closed means it was closed, by the terminal or by the platform. Either is final: open a new session, with a new client_session_token.

Keep the session's id and client_session_token where they survive the app restarting. If the app loses the id but kept the token, open again with the same token: the answer is that session, as it stands now, id and all.

If both are lost, opening a new session is refused with 409 session_already_open while the old one is live. Once it misses its heartbeats it is interrupted, and once its recovery window runs out, 5 minutes after its last heartbeat by default, it expires; from then on a new session opens.

Open again with the same client_session_token before renewing the certificate, not after: a token first sent with the old certificate is refused with 409 session_already_open when sent again with the new one.