pixellint

Mobile · engineers · PMs

The MMP endpoint is a contract, not fire-and-forget

S2S is how you report events the SDK never saw: subscriptions from the backend, refunds, anti-fraud rejects. It is also how you double-count if the SDK already sent the same event without a dedup id.

Adjust

s2s.adjust.com/event takes query or form parameters. app_token and event_token are required (vendor.adjust.param.app_token.missing, vendor.adjust.param.event_token.missing). s2s must be 1 so Adjust knows this is not a forged SDK hit (vendor.adjust.param.s2s.missing, vendor.adjust.param.s2s.invalid). You also need a device id: idfa, gps_adid, or another documented id (vendor.adjust.device_id_required).

created_at, when you send it, is ISO 8601, not a Unix epoch you copied from Meta CAPI (vendor.adjust.param.created_at.invalid). Do not hash ip_address. Adjust matches on the clear IP. A SHA-256 there is vendor.adjust.hashed_plaintext_field. A leaked S2S key is a write handle on your app's measurement. Rotate it like an API secret, not like a pixel id you paste into GTM.

https://s2s.adjust.com/event?s2s=1&event_token=f0ob4r&app_token=4w565xzmb54d&idfa=D2CADB5F-410F-4963-AC0C-2A78534BDF1E&created_at=2026-08-21T18:04:00Z&ip_address=192.0.2.1

AppsFlyer

api3.appsflyer.com/inappevent/{app_id} wants JSON. appsflyer_id and eventName are required (vendor.appsflyer.body.appsflyer_id.missing, vendor.appsflyer.body.eventName.missing). The iOS app id on the path needs the id prefix. Digits only still return 200, and the event is not recorded. Pixellint warns vendor.appsflyer.ios_app_id_unprefixed. That is the textbook HTTP 200 is not validation case on this pack.

eventTime, when present, is UTC as yyyy-mm-dd hh:mm:ss.sss, not ISO with a T, not a Unix epoch (vendor.appsflyer.body.eventTime.invalid). Hash email_hashed and the other documented hashed fields. Do not hash ip (vendor.appsflyer.body.hashed_plaintext_field). eventValue is documented as required, including as an empty string when there is no value. Empty is a legal payload, so the pack does not contract it. Do not invent a missing-eventValue finding.

POST https://api3.appsflyer.com/inappevent/id123456789
{
  "appsflyer_id": "1234567890123-1234567",
  "eventName": "af_purchase",
  "eventValue": "",
  "eventTime": "2019-05-15 12:17:01.123",
  "eventCurrency": "USD",
  "email_hashed": "62a14e44f765419d10fea99367361a727c12365e2520f32218d505ed9aa0f62f",
  "ip": "192.0.2.1"
}

Branch

api2.branch.io/v2/event/standard and /v2/event/custom need branch_key, name, and user_data (vendor.branch.body.branch_key.missing, vendor.branch.body.name.missing, vendor.branch.body.user_data.missing). At least one of developer_identity, browser_fingerprint_id, idfa, idfv, android_id, or aaid has to identify the device (vendor.branch.body.user_needs_an_identifier).

user_data.ip must not be a digest (vendor.branch.body.hashed_plaintext_field). This endpoint is the event contract, not the deep-link redirect. A 200 on a deep link click is a different host. Do not POST a purchase to the link redirect and expect it to count as an in-app event.

POST https://api2.branch.io/v2/event/standard
{
  "branch_key": "key_live_example",
  "name": "PURCHASE",
  "user_data": {
    "os": "iOS",
    "idfa": "9876F1SS-2983-3855-27RR-2R626772VFNB",
    "ip": "192.0.2.1",
    "developer_identity": "user-10492"
  },
  "event_data": {
    "currency": "USD",
    "revenue": 129.99
  }
}

Do not hash IP

Adjust ip_address, AppsFlyer ip, Branch user_data.ip: plaintext. A 64-character hex string there is unmatchable. The same helper that correctly hashes email_hashed will do this if you map every string through SHA-256. Split the list. Identifiers the vendor named as hashed get hashed. IP does not.

Sending your data-center IP makes every install look like your office. Send the device IP. Pixellint will flag a hashed IP. It will not tell you the unhashed IP was CGNAT. That is a matching problem, not a contract miss.

https://s2s.adjust.com/event?s2s=1&event_token=f0ob4r&app_token=4w565xzmb54d&idfa=D2CADB5F-410F-4963-AC0C-2A78534BDF1E&ip_address=a85e9ca18f34935ab9b0381b25bfad2455444112b0149270fd88e3da172fe196

Retries, clocks, and SKAN

Retry the same body. A new event token or a new timestamp on retry double-counts. Device ids, IDFV, and your user id have to match what the SDK already used or the MMP creates a new user. Honor the vendor clock. Adjust ISO 8601, AppsFlyer space-separated UTC, Branch as documented: three dialects. Copying one client across MMPs is how installs land in 1970 on one of them.

SKAN postbacks are not this endpoint. Do not POST a conversion value to s2s.adjust.com/event and expect Apple to see it. MMP S2S is your event. SKAN is Apple's. Ship both, and validate both as different artifacts. pixellint validate url on the Adjust query, pixellint validate json on the AppsFlyer or Branch body. The packs encode the documented contracts. They do not decode a SKAN postback.

https://api3.appsflyer.com/inappevent/123456789

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.