Dedup & retries
eventId — pixel + CAPI dedup
Platforms that support dual setup (pixel + Conversions API) dedupe on a shared event id. Set the same stable string on the browser pixel and on ConversionEvent.eventId. You get full coverage without double-counting.
// Browser pixel (Meta example) — same eventID the server will send
fbq('track', 'Purchase', { value: 49, currency: 'USD' }, { eventID: orderId });
// Server CAPI — same stable id
await ads.conversions.track({
name: 'purchase',
value: 49,
currency: 'USD',
eventId: orderId, // ← shared with the pixel
transactionId: orderId,
user: { email: buyer.email },
consent: { adUserData: true, adPersonalization: true },
});// When eventId is absent, the client derives:
// `${name}-${eventTime ?? ''}-${transactionId ?? user.email ?? ''}`
// Override whenever you have a better stable id (order id, checkout id).Prefer an order id or checkout id over the auto-derived form whenever you have one — it’s stable across retries and across pixel/server.
DedupeStore — skip already-sent pairs
Optional client hook. Key is ${eventId}:${platform}. After a successful send the client calls add(key); on the next run seen(key) short-circuits dispatch and the result comes back with deduped: true.
import { createAdscapi, type DedupeStore } from 'adscapi';
// You own the store — Redis, KV, Postgres, in-memory for tests.
const dedupe: DedupeStore = {
async seen(key) { return redis.sismember('adscapi:dedupe', key); },
async add(key) { await redis.sadd('adscapi:dedupe', key); },
};
const ads = createAdscapi(process.env, { dedupe });
// key = `${eventId}:${platform}`
// Already-sent keys return { ok: true, deduped: true } and skip the HTTP call.
const results = await ads.conversions.track(event);This is process-level / store-level idempotency — separate from the platform’s own pixel/CAPI dedup. Use both.
Retries
// Built into every platform dispatch (src/retry.ts): // - 3 tries // - retry on 5xx, 429, and network errors // - 4xx is terminal (bad payload won't fix itself) // - backoff: 200ms, 400ms, 800ms (2 ** attempt * 200)
Adapters throw on failure; the client wraps with withRetry and reports per-destination. One platform failing never blocks the rest — see Errors.
DestinationResult flags
type DestinationResult = {
platform: string;
ok: boolean;
error?: string;
dryRun?: boolean; // opts.dryRun
deduped?: boolean; // DedupeStore saw this eventId:platform
};Related
- conversions.track()
- Pixel helpers — capture click ids; pair with a shared
eventId - Match quality
- Errors