PlaidEx.Webhooks.Deduplicator (plaid_ex v1.0.0)

Copy Markdown View Source

ETS-based webhook deduplication with sliding expiry window.

Plaid may deliver the same webhook more than once (at-least-once delivery semantics). This module maintains a sliding window of seen webhook IDs to detect and discard duplicates.

Architecture

Uses a GenServer to own the ETS table and run periodic cleanup. The fast path (check + record) is a single ETS operation — no GenServer call required for normal traffic.

Webhook ID derivation

Plaid does not include a stable unique ID in all webhook payloads. IDs are derived from:

  • webhook_type + webhook_code + item_id + quantized timestamp (5s window)

This means the same event delivered twice within 5 seconds is deduplicated. Events delivered more than 5 seconds apart are treated as distinct (edge case — rare in practice).

Window size

Default: 1 hour. Configurable via :webhook_dedup_window_seconds. For high-volume deployments, reduce this if ETS memory is a concern.

Summary

Functions

Checks if a webhook has been seen before.

Returns a specification to start this module under a supervisor.

Derives a deduplication ID from webhook event fields.

Functions

check_and_record(webhook_id)

@spec check_and_record(term()) :: :ok | {:error, :duplicate}

Checks if a webhook has been seen before.

If not seen, records it and returns :ok. If already seen, returns {:error, :duplicate}.

This is an atomic check-and-set using ETS insert_new/2.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

derive_id(event)

@spec derive_id(map()) :: String.t()

Derives a deduplication ID from webhook event fields.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()