Backfilling order history
Question: The merchant wants past orders loaded into Diller so members start with points and level.
Decide first: should you?
Section titled “Decide first: should you?”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.
Only for consenting members
Section titled “Only for consenting members”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.
Doing it safely
Section titled “Doing it safely”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:
- Dry run first. Count what would be sent and show the merchant before anything is written.
- Throttle. A few requests per second. A backfill is not urgent, and hammering the API affects the merchant’s live traffic.
- 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. - Post exactly as your live integration would. We reject a
transaction whose
external_id,origin.department_idandorigin.system_idall match one we already have, with200andis_ok: false. Use the sameORDER-{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 ownsystem_idand that protection is gone. Record each success so a re-run skips the work.
Tell us before a large load
Section titled “Tell us before a large load”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.
Verify afterwards
Section titled “Verify afterwards”- Point totals for a sample of members match what you expected.
- No duplicate transactions — spot-check a few
external_idvalues. - Membership levels moved as intended, not further.
- Nothing landed in the wrong department or with
channel: POS.
See also: Transactions, Duplicates.
