Skip to content

Handle errors and retries

Which errors to retry, how long to wait, and what to log so support can help.

Version v1.1, updated

On this page

You will make your integration recover on its own from the errors that pass, and stop on the ones that need a fix. The status and the code tell you which is which.

Before you start

Read Errors for the members of a problem, and Idempotency for safe retries of writes.

Retry or fix

Answer

Retry?

How

A timeout or a network error

Yes

With the same idempotency key, after a short wait.

429 rate_limited

Yes

After the Retry-After seconds.

409 idempotency_in_progress

Yes

After a short wait, with the same key.

409 numbering_conflict

Yes

At once, with the same key.

500 internal_error

Yes, a few times

With the same key and a growing wait.

Any other 4xx

No

Fix the request, the key or the data. The same request gives the same answer.

Steps

  1. Create the idempotency key once per write, before the first attempt.

  2. On a retryable answer, wait the Retry-After seconds when the header is there. Otherwise double the wait each time, from one second up to a minute, with some randomness.

  3. Stop after a few attempts and report the failure with its request id.

  4. On any other error, log it and move the record aside for a person to look at.

Retry with backoff
async function callBirp(path, init = {}, attempts = 5) {
  // One idempotency key for every attempt of the same write.
  const headers = {
    Authorization: `Bearer ${process.env.BIRP_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    ...init.headers,
  };
  for (let attempt = 1; ; attempt++) {
    const response = await fetch(`https://api.birp.io${path}`, { ...init, headers });
    const retryable = response.status === 429 || response.status >= 500;
    if (!retryable || attempt === attempts) return response;
    const after = Number(response.headers.get("Retry-After"));
    const wait = after > 0 ? after * 1000 : Math.min(60000, 1000 * 2 ** attempt) * (0.5 + Math.random());
    console.warn("BIRP retry", response.status, response.headers.get("X-Request-Id"));
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
}

Log what support needs

  • The request_id of the answer, or X-Request-Id, and the time of the request.

  • The method, the path, the status and the code.

  • Never the API key, and never a whole request body with customer data.

Check the result

Force a 422 on purpose, for example with an unknown field. Check that your integration logs the code and the request id, and does not retry.

What can go wrong

Retrying a 4xx unchanged repeats the error and uses up your rate limit. Retrying a write without the same idempotency key can create it twice.

  • errors-overview
  • idempotency
  • rate-limits