Response Codes and Retry Logic

Sometimes API requests fail, even when everything is configured correctly.

This can happen due to:

  • Network issues
  • Short-term rate limits (429)
  • Service Temporarily Unavailable (503)

To handle these gracefully, we recommend using exponential backoff with jitter, along with idempotency keys for safe retries of order creation requests. This strategy helps your integration retry intelligently by spacing out attempts over time while reducing the risk of overloading our system.

Exponential Backoff

Exponential backoff is a retry strategy where the delay between each retry increases exponentially, typically doubling after each failed attempt.

This allows your application to:

  • Quickly retry after short-lived issues (e.g., network hiccups)
  • Back off rapidly when a system is overwhelmed
  • Reduce the chance of overwhelming APIs or retrying too aggressively.
delay = initialDelay * (factor ^ retryNumber)

Where

  • initialDelay is the base delay (e.g., 30000 ms)
  • factor is the exponential multiplier (commonly 2)
  • retryNumber is the current attempt (starting at 0)

Jitter

Jitter adds randomness to your retry delays to avoid the thundering herd problem, where many clients retry at the same time and unintentionally flood the API.

Instead of all clients retrying exactly at 2 seconds, some will retry at 1.7s, others at 2.4s, and so on — spreading out traffic.

We recommend full jitter, which randomly selects a wait time between 0 and the calculated exponential backoff.

actualDelay = random(0, exponentialDelay)

This is widely considered the most effective jitter strategy in practice.

Retry Policy Recommendation

We recommend the following exponential backoff with jitter strategy when making requests to PostGrid’s API:

  • Retry up to 5 times
  • Spread retries over a 30-minute period
  • Use idempotency keys when creating orders

Idempotency: Prevent Duplicate Orders

If you’re making a POST request to create an order (e.g. letters, postcards, cheques, self_mailers, or boxes), always use an Idempotency-Key.

This makes your requests safe to retry, even if your first attempt timed out or failed mid-transaction. If your request validates successfully, we will ensure that subsequent requests with an identical idempotency key made up to 24 hours after the initial will return the exact same response without creating any orders. This includes error responses.

Include this header:

Idempotency-Key: <your-unique-key>

You are free to generate your keys however you like, but we suggest using V4 UUIDs.

Example Implementation

import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';

// Retry schedule in milliseconds (~30 min total)
const retrySchedule = [30_000, 60_000, 120_000, 300_000, 900_000]; // 30s, 1m, 2m, 5m, 15m

function getJitteredDelay(baseDelay) {
  return Math.random() * baseDelay;
}

async function retryCreateLetterWithBackoffAndIdempotency() {
  const idempotencyKey = uuidv4();

  for (let attempt = 0; attempt < retrySchedule.length; attempt++) {
    try {
      const response = await axios.post(
        'https://api.postgrid.com/v1/letters',
        {
          // your body here
        },
        {
          headers: {
            'x-api-key': // <postgrid-api-key>,
            'Idempotency-Key': // <your-idempotency-key>,
            'Content-Type': 'application/json',
          },
        }
      );

      console.log('Letter created successfully!');
      return response.data;

    } catch (error) {
      const status = error?.response?.status;
      const isRetryable = [429, 503].includes(status);

      if (!isRetryable || attempt === retrySchedule.length - 1) {
        console.error('Letter creation failed permanently.');
        throw error;
      }

      const delay = getJitteredDelay(retrySchedule[attempt]);
      console.warn(`Attempt ${attempt + 1} failed (${status}). Retrying in ${Math.round(delay / 1000)}s...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}