adinize

Elixir SDK for the adinize server events API. Your server reports conversions to adinize, which attributes them to the ad click and forwards them to Meta, TikTok and Google Ads.

Personal data leaves your server only as SHA-256 hashes.

Install

def deps do
[{:adinize, "~> 0.1"}]
end

Create a secret key on your pixel's page in adinize, and keep it on your server:

# config/runtime.exs
config :adinize,
secret_key: System.fetch_env!("ADINIZE_SECRET_KEY"),
default_country: "EG"

Send an event

Adinize.track("Purchase",
event_id: "order_10482",
user: [email: " Jane@Example.com ", phone: "0100 123 4567"],
data: [value: 129.5, currency: "EGP", order_id: "10482"]
)
#=> {:ok, %Adinize.Result{event_id: "order_10482", status: :accepted, errors: []}}

From a Phoenix controller, add the visitor cookie, Meta's cookies, the IP and the User-Agent:

Adinize.track("Purchase", Adinize.Plug.context(conn) ++ [event_id: order.id])

Hashing

The SDK hashes these fields with SHA-256 before the request leaves your server. It trims and lowercases text first, as Meta's customer information rules say.

Field Hashed from
email the trimmed, lowercased address
phone the E.164 digits (see the phone rule above); Meta gets them without the +
first_name, last_name the trimmed, lowercased name
street_address the trimmed, lowercased text

A field that is empty after trimming, or a phone that cannot become E.164, is left out of the request. IP address, User-Agent and the _fbc and _fbp cookies go as given: Meta and TikTok match them unhashed. Adinize.Hash exposes each function if you need a hash elsewhere.

Deduplicate with the browser pixel

When the adinize pixel in the browser and your server both report the same purchase, give both the same event_id, and use the same event name. The platforms keep one of the two: Meta and TikTok merge a browser and a server event that share event_id within 48 hours.

Your server call carries the order number the browser event carries as its event_id:

Adinize.track("Purchase", event_id: "order_10482", data: [value: 129.5, currency: "EGP"])

Your pixel's page in adinize shows how to send an event_id from the browser. A retry from your server is safe for the same reason: adinize answers :duplicate for an event_id its pixel already holds.

Results and errors

Every call returns a value and never raises.

You get When
{:ok, %Adinize.Result{status: :accepted}} stored
{:ok, %Adinize.Result{status: :duplicate}} the pixel already held this event_id
{:ok, %Adinize.Result{status: :rejected, errors: [...]}} the server refused this event, with reasons
{:error, %Adinize.Error{status: 401, code: "INVALID_KEY"}} the key is missing, unknown or revoked
{:error, %Adinize.Error{status: 429, retry_after: 12}} wait 12 seconds, then send the same events again
{:error, %Adinize.Error{code: "INVALID_OPTION"}} a malformed option; nothing was sent

Adinize.Error lists every code. track/2 does not retry unless you pass retry: true (see below).

Sending in the background

A developer who does not want a web request to wait on adinize adds a batcher to the supervision tree and sends asynchronously:

children = [{Adinize.Batcher, flush_interval: 1_000, max_batch: 100}]
Adinize.track_async("Purchase",
event_id: "order_10482",
data: [value: 129.5, currency: "EGP"]
)
#=> :ok

The secret key

The SDK never logs the key and never puts it in an error or in its own telemetry. Finch's telemetry events carry the request headers, key included: if your app logs Finch telemetry metadata, filter the authorization header first.

Contract

The API behind this SDK is described by one OpenAPI file: https://adinize.ai/api/server/v1/openapi.yaml. spec/server-events-v1.yaml holds a copy that CI compares with the live file. Docs: https://hexdocs.pm/adinize.

License

MIT