Blog · X ads
X Conversion API event_id is the Ads Manager UUID. conversion_id is the dedup key.
On X, event_id is not your order id. It is the conversion event created in Ads Manager. Dedup with the pixel uses conversion_id. OAuth 1.0a. Need at least twclid, hashed email, or hashed phone. IP plus UA alone is not enough.
Every other CAPI you already shipped taught the opposite habit. Meta event_id is the dedup key you generate per firing. OpenAI id is the same idea. X puts that job on conversion_id. event_id is the static Event ID Ads Manager shows when you create the conversion under the pixel. Paste your order number into event_id and you have not created a Purchase. You have posted an unknown event type to a pixel that still 200s.
developer.x.com still 403s or 404s for a lot of crawlers. The working official writeup is the X Ads API web conversions page (the docs.x.com Ads API set). It is not Graph. It is not a Bearer token. The first worker that copies Meta user_data onto ads-api.x.com will fail auth, matching, and dedup in three different ways and still look like a networking ticket.
Create the event in Ads Manager, then POST it
The Conversion API needs Ads API access: a developer account, an app approved for Ads API, and user tokens. Tokens used with Conversion API must be for users with AD_MANAGER or ACCOUNT_ADMIN, which you can check via authenticated_user_access. Existing Ads API apps and tokens can be reused. The tokens can be handed to operators who do not themselves hold those roles.
Create an Event Source in Events Manager if you do not already have an X Pixel. That pixel's ID is the pixel_id on the path. Inside that source, Add events, Install with Conversion API. Ads Manager then shows the Pixel ID and the Event ID. The ID of the event is your Event ID. If you want pixel plus API dedup, reuse the existing pixel event rather than creating a second Purchase.
The request is POST /{version}/measurement/conversions/:pixel_id on ads-api.x.com. Current examples use version 12. The body is conversions[], each with conversion_time as an ISO 8601 timestamp, event_id set to that Ads Manager Event ID, identifiers[], and conversion_id when you are deduping. Check for HTTP 200. The example response reports conversions_processed and a debug_id. A retry loop belongs in your worker. A Meta-style test_event_code does not.
conversion_id is the join with the pixel
Official setup language: if you use both pixel and Conversion API for the same event, use the deduplication key in the pixel snippet and on the Conversion API request as conversion_id. Tealium's connector guide repeats that and notes conversion_id is an identifier for a conversion event used to de-duplicate Web Pixel and Conversion API conversions in the same event tag. Your order id, UUID, or event_id from the data layer belongs there.
event_id stays the Ads Manager value. Tealium describes it as the base36 ID of a specific pre-configured event, matching the ID in Events Manager. Official examples look like ol288, not ORDER-8891. Swapping the two fields is the silent double-count or the silent drop. Meta-trained sGTM variables named Event ID will land in the wrong JSON key unless you rename them.
Do not take secondary blogs' 24-hour windows as X's contract unless X writes it. The primary instruction is: same event in Ads Manager, same conversion_id on both pipes. Page-view style events have extra connector notes in partner docs. Stick to the Ads API sentence for Purchase until you have a primary in front of you.
Identifiers, hashing, and the OAuth header
X currently needs at least one of Click ID (twclid), email address, or phone number. If you send IP address or user agent, a second identifier must be sent. IP plus UA by themselves are not enough. Passing more identifiers raises match rate. twclid is parsed from the twclid query parameter after the click. Do not hash it. Store it with the lead or the order so batch jobs still have it.
Email: trim leading and trailing spaces, then SHA-256 without salt. The docs hash test@x.com to d360d510a224510f373931ce2d6215a799f5a9c1cef221b0149b6b6b50cced62. Phone: E.164, then SHA-256 without salt. +11234567890 hashes to 1fa6b8d986d9b9cd01bf36951815158bbde9f520c0567c835dfe34783d0a4231. IP and user agent stay plaintext, spaces trimmed. Hashing the IP because a Meta helper hashed em is how the event becomes unmatchable.
All Ads API calls need an OAuth 1.0a Authorization header over HTTPS to ads-api.x.com. You generate a new header per request. X tells partners to use a known OAuth library; they will not debug a custom signer. Twurl is the CLI they maintain for exploration. A Graph Bearer token on this host is a 401 that looks like a expired secret. It is the wrong protocol.
The documented rate limit is 60,000 events per account per 15 minute interval. Your server still has to log the user action, filter opted-out users, and attach identifiers. Optional commerce fields in the example are value, number_items, description, and contents[] with content_id, content_name, content_type, content_price, num_items, content_group_id. None of those replace event_id or a match key.
The body a Meta worker will emit
Order id in event_id, Ads Manager Event ID in conversion_id, IP plus UA only. Matching and dedup are both inverted.
{
"conversions": [{
"conversion_time": "2026-03-18T16:00:00.000Z",
"event_id": "order_8891",
"identifiers": [{
"ip_address": "203.0.113.9",
"user_agent": "Mozilla/5.0"
}],
"conversion_id": "ol288"
}]
}
Posted to /12/measurement/conversions/{pixel_id} that object can still 200 if the OAuth header is valid. conversions_processed is not Ads Manager attribution. event_id does not name a conversion event the account knows. conversion_id is not doing pixel dedup because the pixel snippet was not given order_8891. identifiers has no twclid, no hashed_email, and no hashed_phone_number.
The documented shape puts the Ads Manager Event ID in event_id, your per-firing key in conversion_id, and at least one of twclid, hashed_email, or hashed_phone_number in identifiers. IP and user_agent may ride along with one of those, not instead of them.
Names that do not translate
- Meta event_id is X conversion_id.
- X event_id is the Ads Manager Event ID, not the order id.
- pixel_id is on the path. event_id is in the JSON.
- twclid is not hashed. hashed_email and hashed_phone_number are SHA-256.
- OAuth 1.0a per request, not a Graph Bearer token.
- conversion_time is ISO 8601, not Unix seconds.
- IP plus user_agent need a second identifier.
We currently need at least one identifier to be passed such as Click ID (twclid), email address, or phone number. If using IP address or user agent, a second identifier must be sent for proper conversion matching.
X Ads API web conversions
What to do
Create or reuse the conversion event in Events Manager. Put that Event ID in every CAPI row's event_id. Generate a per-order conversion_id and send the same value on the pixel. Capture twclid. Hash email and E.164 phone with SHA-256. Sign every request with OAuth 1.0a. Stay under 60,000 events per account per 15 minutes.
Keep one internal event. At the edge, emit the X envelope. Persist x_event_id separately from the Meta event_id column. Do not send IP and UA as the only identifiers. Filter users who opted out before the POST.
The X pack is vendor/x-conversions-api. The Meta pack is vendor/meta-conversions-api. One fixture cannot serve both. Pixellint is not affiliated with X. Passing a linter means the artifact matches the published envelope. It does not mean Ads Manager attributed the conversion, and it does not detect fraud.
Checklist
- POST to https://ads-api.x.com/12/measurement/conversions/{pixel_id} with a fresh OAuth 1.0a header.
- Set event_id to the Ads Manager Event ID. Set conversion_id to your per-conversion dedup key.
- Include at least one of twclid, hashed_email, or hashed_phone_number.
- Do not send IP and user_agent alone.
- Reuse the pixel's conversion_id when both pipes fire.
- Treat HTTP 200 plus conversions_processed as accept, not attribution.
Sources
Contract pages
The dated argument is above. These pages are the field lists.