One call, every ad platform a site has configured.
Pixelex.Destinations.fire("shop", :purchase,
event_id: "order:" <> order.id,
event_source_url: url,
user_data: %{email: patient.email, phone: patient.phone, ip: ip, fbclid: fbclid},
custom_data: %{currency: "EGP", value: 1499.0}
)Each platform no-ops without its own credentials, so a site that set up only Meta behaves exactly as though the others did not exist.
event_id is the whole design
The same id goes to every platform and to the browser pixel. Each platform
deduplicates on it, so firing both legs counts one conversion — and, less
obviously, it is what makes retrying safe. Derive it from the row it
describes ("order:#{order.id}", never UUID.generate()) and a replay can
never double-count. Everything else here depends on that.
Delivery is durable, or it says so
With oban installed, fire/3 enqueues and
Pixelex.Destinations.Worker makes the calls with five attempts. Without it,
delivery falls back to an unsupervised task and logs a warning once — a
deploy mid-flight then drops the conversion with no record and no retry,
which is silent under-reporting of exactly the events ad spend optimises
against.
Secrets are not put in the queue
The job carries a site id and an event name. Credentials are re-read when it runs. Writing decrypted access tokens into a database-backed job table would be strictly worse than the extra read, and rotating a token would leave stale secrets sitting in the queue.
Configuring a site
%{
"meta" => %{"pixel_id" => "…", "access_token" => "…"},
"tiktok" => %{"pixel_code" => "…", "access_token" => "…"},
"snapchat" => %{"pixel_id" => "…", "access_token" => "…"},
"ga4" => %{"measurement_id" => "G-…", "api_secret" => "…"}
}in pixelex_sites.destinations, or under config :pixelex, sites:.
Encrypting them at rest is the host's job — pixelex never logs them, but it
cannot encrypt a column it does not own.
Summary
Functions
The canonical events every destination maps from.
A site's per-platform credentials, keys converted to atoms safely.
The whole canonical-event → platform-dialect table.
Deliver synchronously to every configured platform. Called by the worker.
Queue event for every platform site_id has configured. Always :ok.
Destination modules in play — the built-ins, plus anything configured.
Functions
@spec canonical_events() :: [atom()]
The canonical events every destination maps from.
A site's per-platform credentials, keys converted to atoms safely.
The whole canonical-event → platform-dialect table.
Pixelex.Destinations.dialects()[:purchase]
#=> %{meta: "Purchase", tiktok: "CompletePayment", snapchat: "PURCHASE", ga4: "purchase"}A nil means the platform genuinely has no equivalent.
Deliver synchronously to every configured platform. Called by the worker.
Deliberately not rescued. A raise here fails the Oban job so it retries, which is the entire point of not using a fire-and-forget task.
Queue event for every platform site_id has configured. Always :ok.
Best-effort at the call site by design: a tracking problem is never the reason a booking or a payment fails.
Options
:event_id— required in practice. Derive it from the row.:event_source_url,:action_source:user_data— raw, unhashed. Hashing happens per platform, at the edge.:custom_data—currency,value,content_ids, …:consent— signals fromPixelex.Plug.Context; omitted means no visitor is involved (a cron, an admin action) and no banner applies.
@spec modules() :: [module()]
Destination modules in play — the built-ins, plus anything configured.