Send events and purchases from your server or native app to Adverfly — no browser pixel required.
POST https://b.adverfly.com/e Content-Type: application/json
The same endpoint the browser pixel uses. One event per request, sent in real time: events are timestamped when they arrive, so a batch sent hours later lands on the wrong day and outside its session.
Create a server-side key in Adverfly under Data Exports → Server-side keys and send it with every S2S request:
X-Adverfly-Key: adv_s2s_…
is_s2s: true without a valid key is rejected with 401. Without any key, S2S requests are accepted as before.Authorization: Bearer <key> is accepted as well.| Field | Type | Description |
|---|---|---|
store_id | number | Your Adverfly workspace ID |
event_type | string | "event" for visits and funnel steps (pageview, add_to_cart, initiated_checkout), "conversion" for end goals (purchase, lead) |
name | string | Event name, e.g. "pageview", "add_to_cart", "purchase" |
is_s2s | boolean | Always true for server-to-server requests |
ip | string | The end customer's IP address, as its own top-level field. Never your server's IP |
is_s2s: true and no ip are rejected. The IP is used for visitor matching and forwarded to ad platforms (e.g. Meta CAPI) as the customer's IP. Do not put it into properties.| Field | Type | Description |
|---|---|---|
keys | string[] | Stable visitor identifiers (see Keys). Without keys, visitors can only be matched by IP |
parameters | string | The landing query string of the visit, e.g. "?utm_source=facebook&fbclid=…". This is what assigns the channel and campaign. Send it on the first event of a visit (web: the landing pageview; app: the app open / deep-link event) |
properties | object | Click IDs (see Click IDs) and any custom fields you want to filter or break down by |
hostname | string | A fixed host for this source, e.g. "www.myshop.com" or "app.myshop.com" for an app. Tell us a new host so your reports include it |
pathname | string | Page or screen path, e.g. "/products/sneakers" |
referrer | string | Referring URL, if known |
store_currency | string | Store currency, e.g. "EUR" |
store_timezone | string | Store timezone, e.g. "Europe/Berlin" |
User-Agent header, not from the body. Forward the customer's device or browser user agent in that header, otherwise your server's is stored and forwarded.null, no placeholders.An array of plain-text strings. Send every stable identifier you have; several of the same kind are fine. Adverfly hashes each key (SHA-256) before storage.
| Source | Recommended keys |
|---|---|
Web (Shopify) | Shopify client ID (event.clientId), _ga cookie, _fbp cookie |
Native app | A stable install / visitor ID on every event, plus the shop's customer ID once the user is logged in (a Shopify customer GID is fine) |
app: or cus:. The first 4 characters of a key are stored readable (the rest is hashed), so never start a key with personal data such as an email address.Send the click IDs you have as properties on every event. They are forwarded to the ad platforms from there.
{
"properties": {
"_fbc": "fb.1.1727600000000.IwAR2xYz...",
"_fbp": "fb.1.1727600000000.123456789",
"gclid": "Cj0KCQjw9...",
"gbraid": "0AAAAADx...",
"wbraid": "CjkKEQjw...",
"ttclid": "E.C.P.CqgBkTg...",
"msclkid": "1a2b3c4d5e6f7a8b9c0d...",
"sclid": "7f3e2a1b-...",
"epik": "dj0yJnU9...",
"tblci": "GiDk3m...",
"obclid": "00a1b2c3..."
}
}| Key | Platform |
|---|---|
_fbc | Meta click ID, format fb.1.<timestamp_ms>.<fbclid> |
_fbp | Meta browser ID |
gclid / gbraid / wbraid | Google Ads |
ttclid | TikTok |
msclkid | Microsoft Ads |
sclid | Snapchat |
epik | |
tblci | Taboola |
obclid | Outbrain |
fb.1.<timestamp_ms>.<fbclid> from the fbclid of the landing URL. A bare fbclid in properties is stored but not forwarded to Meta. The raw query string still belongs in parameters for the channel assignment.entry_meta.key / entry_meta.value arrays are still accepted and merged into properties on our side. Use one format, not both.| Field | Type | Description |
|---|---|---|
transaction_id | string | Required. The order ID exactly as your shop sync delivers it to Adverfly. Shopify: the numeric order ID as a string, e.g. "6123456789012" — not the GID, not the order name. Plain text recommended (see Hashing) |
transaction_currency | string | Required. Order currency (converted to the store currency) |
transaction_gross_revenue | number | Recommended. Order total in cents (€99.00 = 9900). Your best estimate is fine |
customer_id | string | Recommended. The customer's email (hashed server-side). Drives new / returning detection and LTV |
is_new_customer | number | Optional. 1 = new, 0 = returning. Detected automatically when omitted |
transaction_shipping_costs, transaction_tax | number | Optional, in cents |
transaction_discount_code, transaction_city, transaction_country_code | string | Optional |
transaction_items | array | Optional line items (see the example below) |
transaction_id. The synced order provides the money (totals, shipping, tax, refunds), the event provides the attribution. That only works when both carry the same ID.400 Transaction ID already exists — for example when the web checkout pixel already reported that order. Treat it as success and do not retry.transaction_id gets a random ID and can never be matched to an order.{
"store_id": 123456,
"is_s2s": true,
"ip": "185.32.100.42",
"event_type": "event",
"name": "pageview",
"hostname": "app.myshop.com",
"pathname": "/products/sneakers",
"store_currency": "EUR",
"store_timezone": "Europe/Berlin",
"keys": [
"app:3f9c2e1a-7b4d-4c8e-9a1f-2d6b8e0c4a7f",
"cus:gid://shopify/Customer/7234567890123"
],
"parameters": "?utm_source=facebook&utm_campaign=autumn&fbclid=IwAR2xYz...",
"properties": {
"_fbc": "fb.1.1727600000000.IwAR2xYz...",
"platform": "ios",
"app_version": "3.4.0"
}
}{
"store_id": 123456,
"is_s2s": true,
"ip": "185.32.100.42",
"event_type": "event",
"name": "add_to_cart",
"hostname": "app.myshop.com",
"pathname": "/products/sneakers",
"store_currency": "EUR",
"store_timezone": "Europe/Berlin",
"keys": [
"app:3f9c2e1a-7b4d-4c8e-9a1f-2d6b8e0c4a7f"
],
"properties": {
"_fbc": "fb.1.1727600000000.IwAR2xYz..."
}
}{
"store_id": 123456,
"is_s2s": true,
"ip": "185.32.100.42",
"event_type": "conversion",
"name": "purchase",
"hostname": "app.myshop.com",
"pathname": "/checkout/thank-you",
"store_currency": "EUR",
"store_timezone": "Europe/Berlin",
"keys": [
"app:3f9c2e1a-7b4d-4c8e-9a1f-2d6b8e0c4a7f",
"cus:gid://shopify/Customer/7234567890123"
],
"transaction_id": "6123456789012",
"transaction_currency": "EUR",
"transaction_gross_revenue": 9900,
"customer_id": "customer@email.com",
"transaction_items": [
{
"transaction_item_id": "SKU-001",
"transaction_item_name": "Sneaker",
"transaction_item_price": 4950,
"transaction_item_quantity": 2
}
],
"properties": {
"_fbc": "fb.1.1727600000000.IwAR2xYz..."
}
}| Field | What happens |
|---|---|
keys | Hashed server-side (SHA-256); the first 4 characters stay readable. Send plain text |
customer_id | Lower-cased, whitespace removed, then hashed (SHA-256). Plain text, or a SHA-256 hex of that normalised value |
transaction_id | Stored as sent. Plain text is recommended: only a plain ID picks up the order totals from your shop sync and is deduplicated against the browser pixel. A SHA-256 hex of the plain ID is accepted and still recognised as the same order, but the event's own revenue is used |
ip | Used for matching and forwarded to ad platforms as the customer's IP |
| Status | Meaning | What to do |
|---|---|---|
200 | Event accepted | — |
400 Transaction ID already exists | This order was already reported | Treat as success, do not retry |
400 (any other message) | Validation error, e.g. a missing required field | Fix the payload; retrying the same request will fail again |
401 | S2S key missing or invalid | Send a valid key in X-Adverfly-Key; do not retry unchanged |
429 / 502 / 503 / 504 | Rejected before processing | Retry with backoff |
500 / timeout | May already have been stored | Do not retry events — only purchases are deduplicated (by transaction_id), every other event would be counted twice |
Adverfly does not check consent server-side; sending an event means you have the legal basis for it. Apply the same rule as for your website tracking. Click IDs and anything that is forwarded to ad platforms typically require marketing consent.
Need help? Contact us at team@adverfly.com