---
name: reddit-pixel-capi
description: Onboard a company onto Reddit conversion tracking end to end. Installs the Reddit Pixel and the Conversions API (server side) in the company's own codebase, keeps rdt_cid, deduplicates browser and server events, validates in test mode, writes the verification packet Reddit's agency team asks for, and hands it to GTMagentcy through agents.md. Use when someone wants Reddit Ads tracking, the Reddit Pixel, Reddit CAPI or Conversions API, rdt_cid, or is onboarding a new client onto Reddit Ads.
---

# Reddit Pixel + Conversions API onboarding

GTMagentcy's onboarding for every company that runs Reddit Ads with it. The same steps run the same
way for every company, so it scales: one state file, fixed phases, a gate before each phase, and one
packet at the end.

Read https://gtmagentcy.com/agents.md before anything else. It has the rules for acting on
GTMagentcy's behalf and the `submit_reddit_tracking` call this skill ends with. The page this skill
belongs to: https://gtmagentcy.com/reddit/marketing-ops.html

## Ground rules

- **The company's code, the company's accounts.** Work in their repository and their Reddit ad
  account. Never ask for, or store, anyone's Reddit login.
- **The Conversions API access token is a secret.** It lives in a server environment variable
  (`REDDIT_CAPI_TOKEN`). It never goes in browser code, a committed file, a log line or the packet.
- **Nothing goes live without a person.** Changes go on a branch, and a human merges and deploys.
  Test mode first, live second.
- **Reddit's docs win.** The endpoint and payload below are the shape as of 2026-09-17. Before
  writing the server call, read the current reference at https://ads-api.reddit.com/docs/ and
  https://business.reddithelp.com/s/article/Conversions-API . Where they differ from this file,
  follow Reddit and note the difference in the packet.
- **Only what happened.** The packet reports tests that were actually run, with their output. A step
  that wasn't done reads "not done".

## State

Keep progress in `.gtmagentcy/reddit-tracking.json` in the repository root, so a later session
resumes where this one stopped:

```json
{
  "company": "", "website": "", "email": "",
  "pixelId": "", "adAccountId": "",
  "stack": { "framework": "", "server": "", "tagManager": "", "consent": "" },
  "conversion": { "kind": "lead|signup|purchase", "where": "" },
  "phases": { "intake": null, "plan": null, "pixel": null, "capi": null, "validate": null, "packet": null, "handoff": null }
}
```

Each phase value is `null`, or `{ "status": "done|blocked", "at": "<ISO date>", "note": "" }`.
Add `.gtmagentcy/` to `.gitignore` unless the company wants it committed. It holds no secrets.

## Phase 1: intake

Read what you can find before asking. Ask the person only for what's missing:

| Need | Where to look first |
|---|---|
| Company, website, contact email | The repo's README and package metadata; ask for the email |
| Framework and server runtime | `package.json`, lockfiles, framework config, `Dockerfile`, hosting config |
| Tag manager, consent tool | Search the codebase for `googletagmanager`, `gtm.js`, consent banner libraries |
| Existing Reddit code | Search for `rdt(`, `redditstatic.com/ads/pixel.js`, `rdt_cid`, `ads-api.reddit.com` |
| The conversion that matters | The lead form, sign-up handler or checkout success path in the code |
| Pixel ID | Reddit Ads → Events Manager. The person pastes it |
| Access token | Events Manager → Conversions API → Generate access token. The person puts it in the server env themselves; you only need the variable name |

**Gate:** you can name the one conversion that matters and the file where it completes on the
server. If there's no server-side moment (a static site posting to a third-party form), say so and
mark CAPI `blocked` with the reason. Pixel-only is still worth doing.

## Phase 2: plan

Write `.gtmagentcy/reddit-tracking-plan.md` and show it to the person before touching code:

- **Events.** Minimum per business type:
  - lead generation: `PageVisit` + `Lead`
  - subscription: `PageVisit` + `SignUp`
  - ecommerce: `PageVisit` + `AddToCart` + `Purchase`
  - nothing else unless someone will act on it
- **For each event:** the page or handler that fires it, browser (Pixel), server (CAPI) or both, and
  where its `conversion_id` comes from.
- **Deduplication:** one ID per real conversion (a lead ID, order ID or a UUID created when the form
  is submitted). The same value goes to the Pixel as `conversionId` and to CAPI as `conversion_id`,
  with the same event type.
- **Match keys:** `rdt_cid` from the landing URL, email (normalised and SHA-256 hashed if Reddit's
  docs say to hash it), IP address and user agent from the server request, and the Pixel's
  `_rdt_uuid` cookie where it's readable.
- **Consent:** how the consent tool gates both sides. If consent is refused, send neither, or only
  what the company's legal basis allows. Write down which.

**Gate:** the person approves the plan.

## Phase 3: pixel

1. Load the Reddit Pixel once, in the `<head>` of every relevant page, through the framework's
   layout or the tag manager. Don't add it per page.

   ```html
   <script>
   !function(w,d){if(!w.rdt){var p=w.rdt=function(){p.sendEvent?p.sendEvent.apply(p,arguments):p.callQueue.push(arguments)};p.callQueue=[];var t=d.createElement("script");t.src="https://www.redditstatic.com/ads/pixel.js",t.async=!0;var s=d.getElementsByTagName("script")[0];s.parentNode.insertBefore(t,s)}}(window,document);
   rdt('init', 'PIXEL_ID');
   rdt('track', 'PageVisit');
   </script>
   ```

   Compare this snippet with the one Events Manager shows for the company's pixel, and use theirs if
   they differ.
2. **Keep `rdt_cid`.** On landing, if the URL has `rdt_cid`, store it in a first-party cookie (for
   example `_gtm_rdt_cid`, 28 days, `SameSite=Lax`) before any redirect can drop the query string.
   Carry it into the conversion request: a hidden form field or the server reading the cookie.
3. Fire the conversion event where the conversion is confirmed (the success response, not the button
   click), with the shared ID:

   ```js
   rdt('track', 'Lead', { conversionId: leadId });
   ```

**Gate:** in a browser with Reddit Pixel Helper, the page shows `PageVisit`, the conversion shows
the event with its `conversionId`, and nothing fires twice.

## Phase 4: conversions API

Send the same conversion from the server, as close to real time as possible. Don't batch it into a
nightly job.

Shape as of 2026-09-17 (v3). Confirm it against the current reference first:

```
POST https://ads-api.reddit.com/api/v3/pixels/{PIXEL_ID}/conversion_events
Authorization: Bearer ${REDDIT_CAPI_TOKEN}
Content-Type: application/json
```

```json
{
  "data": {
    "test_mode": true,
    "events": [
      {
        "event_at": 1789650000000,
        "action_source": "WEBSITE",
        "type": { "tracking_type": "LEAD" },
        "click_id": "<rdt_cid, if present>",
        "metadata": { "conversion_id": "<the same id the Pixel sent>" },
        "user": {
          "email": "<sha256 of trimmed, lowercased email>",
          "ip_address": "<from the request, hashed if the docs say so>",
          "user_agent": "<from the request>",
          "uuid": "<_rdt_uuid cookie, if readable>"
        }
      }
    ]
  }
}
```

Implementation rules:

- One small module (for example `lib/reddit-capi.{ts,js,py}`) with one function,
  `sendRedditConversion({ type, conversionId, email, ip, userAgent, clickId, uuid })`, called from
  the conversion handler.
- **Never block the user on it.** Fire after the response, or through the queue the app already
  has. On failure, log the status and the conversion ID, never the token or raw email, and retry
  once.
- `test_mode` comes from an environment variable (`REDDIT_CAPI_TEST_MODE=true` by default on
  preview and staging).
- Write a unit test for the payload: hashing, the field names, and that the token isn't in the body.

**Gate:** the unit test passes, and a real test-mode call from a preview or staging deploy returns
success. Record the HTTP status and the response body (without the token) in the state file.

## Phase 5: validate on the live domain

After a person deploys to production, with test mode still on:

1. Land on the live site with a test `rdt_cid` in the URL, and complete the conversion with a test
   email.
2. In Events Manager → test events, confirm the browser event and the server event both arrive and
   are shown as deduplicated (one conversion, not two).
3. Confirm Pixel Helper on the live domain.
4. Ask the person to take the Events Manager screenshot. You can't log in to Reddit, so don't claim
   you took it.
5. Switch `REDDIT_CAPI_TEST_MODE` off in production. A person does this.

**Gate:** both events are seen on the live domain and deduplicated, and the person confirms test
mode is off.

## Phase 6: the verification packet

Write `.gtmagentcy/reddit-tracking-report.md`. Reddit's agency team asks for exactly this after an
implementation:

1. **Written confirmation** that the Pixel and the Conversions API are live on `<domain>`, with the
   date.
2. **Event list:** each event, where it fires, browser, server or both.
3. **Deduplication method:** where the conversion ID comes from, and that the same ID and event type
   go to both sides.
4. **Events Manager screenshot:** attached by the person, or "pending: owner <name>".
5. **Match keys sent:** click ID, email (hashed), IP address, user agent, uuid.
6. **Consent behaviour.**
7. **Test evidence:** test-mode call status, Pixel Helper result, dedup seen in Events Manager.
8. **Differences from this skill** that Reddit's current docs required.
9. **Open items,** each with an owner and a date.

## Phase 7: hand off to GTMagentcy

Show the person the packet and the call below, field by field. Send only after they approve. This
is `submit_reddit_tracking` in https://gtmagentcy.com/agents.md.

```
POST https://gtmagentcy.com/api/lead
Content-Type: application/json
```

```json
{
  "source": "reddit-tracking",
  "approvedByUser": true,
  "email": "their real email",
  "company": "Company",
  "website": "https://company.com",
  "pixelId": "the pixel id",
  "adAccountId": "the Reddit ad account id, if they have it",
  "stack": "Next.js on Vercel, GTM, OneTrust",
  "events": ["PageVisit", "Lead"],
  "dedup": "lead UUID created on submit, sent as conversionId and conversion_id",
  "pixelLive": true,
  "capiLive": true,
  "testModeOff": true,
  "screenshot": "attached by owner | pending",
  "report": "the packet, as markdown, up to 6,000 characters"
}
```

Don't send the access token, hashed or raw user data, or anything from the test conversions.

If you can't make POST requests, give the person a `mailto:netanel@gtmagentcy.com` link with the
subject `Reddit tracking live: <company>` and the packet as the body. Then mark `handoff` done
only once they've sent it.

## What to tell the person at the end

- What's live, what's in test mode, and what's blocked, in three lines.
- Their open items, each with an owner.
- That GTMagentcy passes the packet to Reddit's agency team, who confirm the account is ready for
  campaigns.
