Blog · Reddit CAPI
Reddit CAPI v3 made event_at milliseconds. A Meta-second clock is a silent miss.
Reddit CAPI v3 made event_at milliseconds. A Meta-second clock is a silent miss. The path is POST /api/v3/pixels/{PIXEL_ID}/conversion_events, the payload sits under data, and a ten-digit event_time copied from Graph is January 1970 as milliseconds.
The migration page lists the break in one table. event_at changed to integer Unix epoch in milliseconds. event_at_ms is deprecated; use event_at. action_source is required. event_type was renamed to type. tracking_type is UPPER_SNAKE_CASE: PAGE_VISIT, not PageVisit. metadata.value is a double in the currency base unit. test_mode is gone; use test_id. That is a new envelope, not a host change.
Meta event_time is Unix seconds, ten digits, at most seven days old. The Meta helper is Math.floor(Date.now() / 1000). Date.now() is already thirteen digits. Copy the helper into a Reddit worker and 1740000000 is a 1970 millisecond timestamp. Reddit still wants events within seven days of when they occurred. A 1970 clock is outside that window even if the JSON parsed.
The path and the wrapper both changed
v2 posted to https://ads-api.reddit.com/api/v2.0/conversions/events/{Pixel ID}. v3 posts to https://ads-api.reddit.com/api/v3/pixels/{PIXEL_ID}/conversion_events. The Pixel ID is in the path. It is not a JSON field named pixel_id next to a Graph token. Bearer tokens from v2 still work. You do not mint a new secret just because the path moved.
Wrap the payload in data. The migration says { "data": /* your current payload */ }. The live Post Conversion Events example is data.events[]. Direct integration snippets show events[] with event_at, action_source, and type. The request you actually POST is the wrapper plus that array. A Graph data[] of event_name objects on ads-api.reddit.com is the wrong array shape on the right host.
Rate limits on the events path: 1000 requests per second, 10000 events per second. Invalid schema or more than 1000 events in a request is a bad request. Direct integration also says 1000 events per request. Batch, but do not dual-send the same event to v2 and v3. Reddit accepts both versions and discourages dual-sending. Roll out a percentage if you have to. Do not fork the purchase.
type.tracking_type is PURCHASE, not Purchase
Standard events moved from PascalCase to UPPER_SNAKE. PageVisit is PAGE_VISIT. ViewContent is VIEW_CONTENT. AddToCart is ADD_TO_CART. AddToWishlist is ADD_TO_WISHLIST. Purchase is PURCHASE. Lead is LEAD. SignUp is SIGN_UP. Custom is CUSTOM. Custom events set tracking_type CUSTOM and custom_event_name to a UTF-8 string up to 64 characters. Promotion Event and PromotionEvent are two events.
action_source is required. Documented values are WEBSITE, APP, PHYSICAL_STORE, OTHER. Meta website is not WEBSITE. Pixel events are website events. Dedup applies only for events in the same channel. Pixel fires dedupe against CAPI WEBSITE, not against APP or PHYSICAL_STORE. PHYSICAL_STORE should not send device ids, click id, UUID, or MAIDs. That is in the help article, not a style preference.
event_source_url is new in v3 for WEBSITE. Reddit parses the domain for Events Manager. Put the click id in the URL when you did not send click_id. metadata replaced event_metadata. value is a double; value_decimal is gone. user.opt_out is gone; send user.data_processing_options with modes LDU when you need Limited Data Use. Country is required for that object. Region is optional.
event_at is Date.now(), not floor seconds
Post Conversion Events documents event_at as int64, required, Unix epoch in milliseconds. The sample value is 1514764800000. That is thirteen digits. Direct integration says send conversions as close to real time as possible, but events must be sent within seven days after they occur. Dedup has a tighter clock: events must be sent within two days for proper deduplication. A late backfill can ingest and still fail to dedupe against the pixel.
v2 had event_at and a separate event_at_ms. v3 has one integer field. If your v2 worker sent event_at_ms, rename it to event_at. If it sent a non-integer event_at, convert it. Do not send both. Do not keep test_mode: true on the new envelope and wonder why production is quiet. test_id is the replacement when you need a test stream.
Match keys can be hashed or raw. Reddit documents SHA-256 hex after its own canonicalize steps for email, phone, MAIDs, and external_id. Email lowercase, strip dots in the local part, strip plus tags. Phone starts with plus and digits only. IP and user_agent stay identifiers you can send in the clear. Hashing them because Meta hashed em is how the event becomes unmatchable. Help center says share IP and click id, and include email and user agent. If you also run the pixel, share UUID.
The body people will send first
A Meta CAPI purchase. This is not a Reddit CAPI v3 event.
{
"data": [{
"event_name": "Purchase",
"event_time": 1749600000,
"action_source": "website",
"event_source_url": "https://shop.example.com/checkout",
"user_data": { "em": ["64ec88ca..."] }
}]
}
That object is a legal Meta CAPI event if the hash is right. Posted to ads-api.reddit.com/api/v3/pixels/{id}/conversion_events it is the wrong wrapper, the wrong event name key, the wrong clock, and the wrong action_source spelling. A gateway that only checks JSON parse can still 200. Reddit will not treat it as a PURCHASE.
The Reddit shape is data.events[] with event_at as thirteen digits, action_source WEBSITE, type.tracking_type PURCHASE, optional event_source_url, metadata.value as a double, and user.email or a hash. click_id is a sibling of event_at, not fbclid. conversion_id on metadata is how pixel plus CAPI dedupe when you send both pipes.
Names that do not translate
- v2 /api/v2.0/conversions/events/{id} is v3 /api/v3/pixels/{PIXEL_ID}/conversion_events.
- Graph data[] of events is Reddit data.events[].
- event_name Purchase is type.tracking_type PURCHASE.
- event_time in seconds is event_at in milliseconds.
- event_at_ms is event_at. test_mode is test_id.
- action_source website is WEBSITE. Also APP, PHYSICAL_STORE, OTHER.
- event_metadata is metadata. value is a double, not value_decimal.
- user.opt_out is user.data_processing_options.
- Dedup is per channel. Pixel does not dedupe against APP.
Dedup is hourly and per channel
If you implement Pixel and CAPI for the same events, Reddit requires dedup. It runs every hour. The event with more metadata and match keys wins. If quality is equal, the earliest event is preferred. Conversion ID is preferred when present on both pipes. Otherwise session-based dedup uses UUID or external_id, with a session defined as no more than five minutes between consecutive events.
Generate a unique conversion_id per distinct event. Reusing one id across many purchases marks them as duplicates and they will not attribute. Events must match event type, and custom events must match the name. A log of deduplicated events is available for up to seven days. If you send different event names on pixel vs CAPI, you do not need to dedupe. You also do not join them.
Help center and direct integration both say use Pixel plus CAPI together. The server hop is more resilient. The pixel still captures what the browser saw. conversion_id is the join, the same idea as Meta event_id with a different field name. Reuse it. Do not invent a second purchase id because the Reddit column was empty in your Meta table.
event_at changed to integer type, representing the Unix epoch in milliseconds.
Reddit CAPI v2 to v3 migration
What to do
Keep one internal event: name, UTC instant, order id, email, landing URL, click references. At the edge, emit the Reddit envelope. Convert the clock there: pass milliseconds as event_at. Do not reuse the Meta floor-seconds helper. Persist conversion_id as a different column from Meta event_id. Capture rdt_cid on the landing hit and copy it through checkout, on click_id or on event_source_url.
Rename event_type to type. Spell tracking_type PURCHASE. Wrap in data. Drop test_mode. Send action_source WEBSITE for web. Lint the body you POST. The Reddit pack is vendor/reddit-conversions-api. The Meta pack is vendor/meta-conversions-api. One fixture cannot serve both.
The contract pages are the v3 migration guide and Post Conversion Events. This post is the market fact: Reddit's clock is milliseconds, and a Meta-second helper is a silent miss. Pixellint is not affiliated with Reddit. Passing a linter means the artifact matches the published v3 envelope. It does not mean Ads Manager attributed the order.
Checklist
- POST to https://ads-api.reddit.com/api/v3/pixels/{PIXEL_ID}/conversion_events with a Bearer token.
- Wrap events in data. Send event_at as integer milliseconds, not Meta seconds.
- Set action_source WEBSITE, APP, PHYSICAL_STORE, or OTHER. Use type.tracking_type PURCHASE, not Purchase.
- Replace test_mode with test_id. Replace event_at_ms with event_at. Replace event_metadata with metadata.
- Put conversion_id on metadata when you also fire the pixel. Dedup is per channel and needs the event within two days.
- metadata.value is a double. Leave ip_address and user_agent in the clear unless you followed Reddit's hash rules.
Sources
Contract pages
The dated argument is above. These pages are the field lists.