Skip to content

Backfilling order history

Question: The merchant wants past orders loaded into Diller so members start with points and level.


Backfilling awards points retroactively. That has commercial consequences the merchant owns, not you:

  • Members may jump several tiers overnight.
  • Point-based coupons may become redeemable immediately.
  • If points expire on a rolling window, old orders may award points that expire almost at once — which looks broken to customers.

Get the merchant to decide the cut-off in writing before you load anything. “Everything we have” is rarely what they actually want.


A transaction can only be posted against a member, and a member only exists with consent. So a backfill covers orders from people who are already members. Do not create members from historical order data — you have no consent for those people.

flowchart TD A["Historical orders"] --> B{"Customer linked to<br/>a Diller member?"} B -->|No| C["Skip.<br/>No consent, no member."] B -->|Yes| D{"After the agreed<br/>cut-off date?"} D -->|No| C D -->|Yes| E["POST /transactions<br/>with the original created_at"]

async function backfill(orders, { cutoff, dryRun = true, ratePerSec = 5 }) {
const gap = 1000 / ratePerSec;
const report = { sent: 0, skipped: 0, failed: [] };
for (const order of orders) {
if (order.createdAt < cutoff) { report.skipped++; continue; }
const memberId = await linkedMemberId(order.customerId);
if (!memberId) { report.skipped++; continue; }
if (await alreadySent(order.id)) { report.skipped++; continue; }
if (dryRun) { report.sent++; continue; }
try {
const result = await postTransaction(memberId, order); // original created_at, with offset
const duplicate = result.details === 'You cannot add duplicate transaction.';
if (!result.is_ok && !duplicate) throw new Error(result.details);
await markSent(order.id); // a duplicate was already recorded
report.sent++;
} catch (err) {
report.failed.push({ orderId: order.id, error: String(err) });
}
await new Promise(r => setTimeout(r, gap));
}
return report;
}

Four things that matter here:

  1. Dry run first. Count what would be sent and show the merchant before anything is written.
  2. Throttle. A few requests per second. A backfill is not urgent, and hammering the API affects the merchant’s live traffic.
  3. Send the original created_at, with its correct UTC offset — not today’s date. Otherwise every historical order lands as though it happened during the import, and the merchant’s reporting is ruined.
  4. Post exactly as your live integration would. We reject a transaction whose external_id, origin.department_id and origin.system_id all match one we already have, with 200 and is_ok: false. Use the same ORDER-{id} and the same origin as live, and an order that was already posted is caught as a duplicate rather than counted twice. Give the backfill its own system_id and that protection is gone. Record each success so a re-run skips the work.

Flag a backfill of any size with us in advance. It is not a rate-limit issue so much as a courtesy: unexplained bulk writes look like a runaway integration.


  • Point totals for a sample of members match what you expected.
  • No duplicate transactions — spot-check a few external_id values.
  • Membership levels moved as intended, not further.
  • Nothing landed in the wrong department or with channel: POS.

See also: Transactions, Duplicates.