Blog · Reddit and Meta
Reddit CAPI vs Meta CAPI stores event_at in milliseconds and WEBSITE in uppercase.
Reddit CAPI vs Meta CAPI is the same purchase on two clocks. Meta event_time is Unix seconds, at most ten digits, and the event has to fall inside about seven days. action_source for a page is website. Reddit CAPI v3 posts to ads-api.reddit.com /api/v3/pixels/{pixel_id}/conversion_events. event_at is exactly thirteen digits, milliseconds. action_source is WEBSITE, APP, PHYSICAL_STORE, or OTHER.
Math.floor(Date.now() / 1000) is the helper a Meta worker already has. Posted as Reddit event_at, those ten digits are milliseconds since the epoch, which is January 1970. The HTTP status can still be 200. The conversion is not in the window anyone is optimizing. Date.now() is already the Reddit unit. Dividing it because Meta needed seconds is the bug.
v2 used an ISO event_at. v3 does not. A string clock left over from v2, or from a TikTok 1.0 timestamp, is a third type on a field that wants an integer. Pixellint flags a ten-digit event_at as vendor.reddit-conversions-api.body.event_at.invalid.
WEBSITE is not website, and the type is not event_name
Meta spells the web platform website, lower case, inside action_source. Reddit spells WEBSITE. The same letters in the wrong case are a different enum value. A shared serializer that lowercases every platform string will pass Meta and miss Reddit.
The conversion type is type.tracking_type, UPPER_SNAKE, not event_name Purchase. The pixel id lives in the path, the way a Graph pixel id lives in the path, and the events sit under data.events[]. Copying a Meta data[] of objects with event_name and event_time does not fill tracking_type or event_at.
test_mode is gone on v3. A flag copied from an older Reddit client, or from Meta test_event_code, is not how you divert a row. Meta's test code keeps the production pixel id and shows the row in Test Events. Reddit's current path does not offer that switch. A test purchase on the live pixel is a live purchase.
Dedup and the currency code
Reddit's dedup against the pixel is weaker than Meta's event_name plus event_id plus Pixel ID. Mint a stable id for retries of the same Reddit event so a 500 does not become two PURCHASE rows. Replays keep the original event_at. Refreshing the clock on retry moves the event. Minting a new id records a second one.
metadata.currency, when present, is an ISO 4217 code such as USD, not a dollar sign. Meta Purchase wants custom_data.value and currency. The names do not match, and a missing currency on Meta Purchase is an optimization with no money. On Reddit, send the code you actually charged.
Hash email the way Reddit documents it. Leave IP and user agent in the clear, the same rule as Meta. A hashed IP matches nobody on either pipe. fbc and fbclid do not fill a Reddit click id. Store the Reddit click parameter from the landing URL the way you store fbclid, on your session, and send that token on the Reddit body only.
The Meta clock on the Reddit field
The Meta integer is seconds. Reddit wants that instant in milliseconds. Ten digits in event_at read as 1970.
meta: event_time 1759010400, action_source website, event_name Purchase
reddit: event_at 1759010400000, action_source WEBSITE, type.tracking_type PURCHASE
One internal instant, two serializers. The Meta edge floors milliseconds to ten-digit seconds and sets website and Purchase. The Reddit edge keeps the milliseconds, sets WEBSITE, and sets the tracking type Reddit lists. A single JSON blob posted to both hosts fails one of them on the unit and the other on the spelling.
The Reddit pixel is a separate image on alb.reddit.com. It will not appear in a filter for facebook.com/tr. Server events will not appear in the browser Network tab unless the page proxied them. Paste the JSON into the playground, or run pixellint validate json with the Reddit pack, before the queue drains.
The pixel host is not the CAPI host
The Reddit pixel is an image on alb.reddit.com. Filter Network for that host when you are proving the browser pipe. facebook.com/tr will not show it. The server call is ads-api.reddit.com and will not appear in the page's Network tab unless you proxied it. A marketer who screenshots Pixel Helper has not looked at Reddit at all.
v3 put the pixel id in the path and the events under data.events[]. type.tracking_type is UPPER_SNAKE. A Meta event_name of Purchase does not fill that field. action_source WEBSITE is uppercase. A serializer that lowercases platform strings to satisfy Meta's website will miss Reddit's enum in the same function if you forget to branch.
test_mode does not exist on v3. Meta test_event_code is not a Reddit field. A test order posted to the live pixel is a live conversion. Keep fixtures on example data, and keep production event_at on the real instant in milliseconds.
Currency and value belong in metadata when you send them, as a number for the value and an ISO code for the currency. A Meta custom_data object pasted beside event_at does not populate metadata. The Reddit pack checks the clock digits. It will not invent a tracking type from the word Purchase. Build the Reddit object in its own function, next to the Meta function, from the same order row.
Retries keep the millisecond instant
Mint one id for the Reddit event and send it again on a timeout. A new id is a second PURCHASE. Keep the original event_at. Refreshing it to Date.now() on the retry attributes the purchase to the retry, and a late retry of a ten-digit value is still 1970. Meta's retry rule is the same idea in seconds: reuse event_id and the original event_time, because the first arrival keeps the value for about 48 hours.
metadata.currency is an ISO 4217 code when you send it. A dollar sign is not USD. Meta wants custom_data.currency on Purchase for the same reason: the optimization needs a currency code, not a symbol. The field names differ. The code you send should be the one you charged.
v2's ISO event_at is not a fallback. A worker that still has a timestamp string from the v2 client, or from TikTok 1.0, will not satisfy a thirteen-digit integer field. Convert at the edge from the stored instant. Do not paste the string through.
Store the instant as milliseconds the moment the order is paid. Every vendor edge reads that one integer. Reddit copies it. Meta floors it to seconds. LinkedIn copies it. TikTok 2.0 floors it. TikTok 1.0 formats it as ISO. A column that already stored ten-digit seconds has to be multiplied by 1000 before Reddit or LinkedIn will see this decade.
What does not transfer
- event_time in seconds becomes event_at in milliseconds. Append three zeros only when you know the source was seconds.
- action_source website becomes WEBSITE.
- event_name Purchase becomes type.tracking_type in UPPER_SNAKE.
- The pixel id is in /api/v3/pixels/{pixel_id}/conversion_events, under data.events[].
- Do not send test_mode. Do not send an ISO event_at left over from v2.
- Keep one id for retries, and keep the original event_at.
- A 200 is not a conversion inside the optimization window.
Sources
Contract pages
The dated argument is above. These pages are the field lists.