Recipes

Keep item codes in sync with the change feed

Poll the change feed with a cursor, or take webhooks, so a correction or a withdrawal reaches your item codes inside the 24 hours the terms allow.

This is a worked example, not a supported connector. Interchange does not build or run connectors. You own the code you write from it, and the support boundary applies. Read the support boundary.

This recipe needs an API key on a plan that includes the endpoint. Read the key from an environment variable and never put it in a web page.

How it works

  1. Start with since, then keep the cursor

    Your first call sends since. Every response has a next_cursor: save it and send it as cursor on the next call, without since. An empty page means you have caught up, and the cursor is still where to resume.

  2. Apply a page, then save its cursor

    Save the cursor only after you have applied the page. If you stop part-way, the next run repeats a page, and your handler must cope with that.

  3. Be idempotent

    Use event_id to ignore an event you have handled. Webhook retries and re-read pages deliver the same event again.

  4. Treat a withdrawal as urgent

    A withdrawn item must stop being shown. Hide it first, then relink it to replaced_by if one is given.

  5. Webhooks are the same events, pushed

    If you take webhooks instead, verify the signature and the timestamp first (see the reference), then run the same handler. Keep polling as a safety net.

The code

Node 22, no libraries. Adapt it to your language and your storage.

JavaScript: read the change feed from a cursor
const BASE = "https://api.interchangeparts.com.au";
const KEY = process.env.INTERCHANGE_API_KEY; // only the keyed endpoints need it

async function call(path, init = {}) {
  const response = await fetch(BASE + path, {
    ...init,
    headers: { Accept: "application/json", ...(KEY ? { Authorization: "Bearer " + KEY } : {}), ...init.headers },
  });
  // Errors are application/problem+json, success is application/json. Both parse as JSON.
  return { status: response.status, body: await response.json(), requestId: response.headers.get("Interchange-Request-Id") };
}

// store is yours: anything that can save and load one string and one set of handled event IDs.
export async function syncChanges(store, apply) {
  let cursor = await store.loadCursor();
  const first = cursor ? "cursor=" + encodeURIComponent(cursor) : "since=" + encodeURIComponent(await store.loadStart());

  let query = first;
  for (;;) {
    const { status, body, requestId } = await call("/v1/changes?limit=25&" + query);
    if (status === 429) throw new Error("Rate limited: wait for Retry-After, then run again");
    if (status !== 200) throw new Error(body.code + " (request " + requestId + ")");

    for (const change of body.changes) {
      // The same event can arrive twice (a webhook retry, or a page re-read), so ignore one already handled.
      if (await store.hasHandled(change.event_id)) continue;
      await apply(change);
      await store.markHandled(change.event_id);
    }

    // Save the cursor only after the page is applied. An empty page means you have caught up, and the cursor is still
    // where to resume from.
    await store.saveCursor(body.next_cursor);
    if (body.changes.length === 0) return;
    query = "cursor=" + encodeURIComponent(body.next_cursor);
  }
}

// What to do with each kind of change. Treat a type you do not know as "look the item up again".
export async function applyChange(change, products) {
  switch (change.type) {
    case "item.withdrawn":
      await products.hide(change.ix_id); // stop showing it now
      if (change.replaced_by) await products.relink(change.ix_id, change.replaced_by);
      break;
    case "item.retired":
      if (change.replaced_by) await products.relink(change.ix_id, change.replaced_by);
      break;
    default:
      await products.refresh(change.ix_id); // published, updated, or a type added since you wrote this
  }
}

Watch for

  • A cached answer may be kept for 12 hours at most, and a correction or withdrawal must reach what you show within 24 hours. Poll at least hourly.
  • Treat a change type you do not know as a reason to look the item up again. Types may be added within a version.
  • The change feed needs an API key on a plan that includes it.

The calls are described in the API reference.