Server-side conversion tracking for Reddit Ads using Shopify webhooks and Reddit Conversions API. This implementation bypasses browser-based tracking limitations (ad blockers, privacy settings, iOS restrictions) to capture conversions that client-side pixels miss.
Reddit officially recommends using both Pixel and Conversions API together for optimal tracking accuracy. According to their documentation:
"CAPI is more resilient to signal loss because it operates server-side, making it less susceptible to ad blockers and browser restrictions."
Client-side pixel tracking loses 30-50% of conversions due to:
- Ad blockers
- Browser privacy settings (Safari, Firefox)
- iOS App Tracking Transparency
- Third-party cookie blocking
This server-side implementation captures conversions that browser-based tracking misses by sending data directly from your server to Reddit's API.
- ✅ Production-ready Docker setup
- ✅ HMAC webhook verification (Shopify security)
- ✅ PII hashing (SHA-256 for privacy)
- ✅ Event deduplication (PostgreSQL)
- ✅ Automatic retry logic with exponential backoff
- ✅ Rate limiting handling (429 responses)
- ✅ Comprehensive logging (Winston)
- ✅ Health check endpoint
- ✅ SSL/HTTPS support (Nginx reverse proxy example)
Prerequisites:
- Linux server with Docker installed
- Domain/subdomain pointing to your server
- Reddit Ads account with Pixel created
- Shopify store with admin access
Installation (5 minutes):
# Clone repository
git clone https://github.com/91369673/reddit-pixel-shopify-server-side-tracking.git
cd reddit-pixel-shopify-server-side-tracking
# Copy environment template
cp .env.example .env
# Edit .env with your credentials
nano .env
# Start services
docker compose up -d
# Check logs
docker compose logs -f reddit-capi- Go to Reddit Ads Manager → Events Manager
- Create new Pixel
- Copy Pixel ID (format:
t2_abc123) - Go to Settings → Conversions API
- Generate Access Token
- Add to
.env:
REDDIT_PIXEL_ID=t2_your_pixel_id
REDDIT_ACCESS_TOKEN=your_access_token_here- Shopify Admin → Settings → Notifications → Webhooks
- Create webhook:
- Event: Order creation
- Format: JSON
- URL:
https://your-domain.com/webhooks/shopify - API version: Latest
- Copy Signing Secret → add to
.env:
SHOPIFY_WEBHOOK_SECRET=your_webhook_secret- Restart:
docker compose restart reddit-capi
Create /etc/nginx/sites-available/reddit-tracking.conf:
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}Enable and reload:
ln -s /etc/nginx/sites-available/reddit-tracking.conf /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginxShopify Store (order created)
|
v
Shopify Webhook (HTTPS POST)
|
v
Nginx Reverse Proxy (SSL termination)
|
v
Node.js Application (Docker container)
|
v
1. HMAC Verification
2. Event Transformation
3. PII Hashing (SHA-256)
4. Deduplication Check
5. Send to Reddit CAPI
6. Log Result
|
v
Reddit Ads Platform
| Variable | Required | Description | Example |
|---|---|---|---|
SHOPIFY_WEBHOOK_SECRET |
Yes | Shopify webhook signing secret | ba32d1d12da1ffc... |
REDDIT_PIXEL_ID |
Yes | Reddit Pixel ID from Events Manager | t2_abc123 |
REDDIT_ACCESS_TOKEN |
Yes | Reddit Conversions API access token | eyJhbGciOiJSUzI1... |
DB_PASSWORD |
Yes | PostgreSQL database password | strong_password |
PORT |
No | Application port (default: 3000) | 3000 |
Health check:
curl https://your-domain.com/healthExpected response:
{
"status": "healthy",
"timestamp": "2025-11-22T12:00:00.000Z",
"uptime": 12345.67,
"database": "connected"
}Send test webhook from Shopify:
- Shopify Admin → Settings → Notifications → Webhooks
- Click on your webhook
- "Send test notification"
- Check logs:
docker compose logs -f reddit-capi
Verify in Reddit:
- Reddit Ads Manager → Events Manager
- Click your Pixel → "Test Events" tab
- Event should appear within a few minutes
View logs:
docker compose logs -f reddit-capiCheck recent events:
docker compose exec postgres psql -U reddit_capi -d reddit_capi -c \
"SELECT event_id, event_type, status, created_at FROM events ORDER BY created_at DESC LIMIT 10;"Failed events:
docker compose exec postgres psql -U reddit_capi -d reddit_capi -c \
"SELECT event_id, error_message FROM events WHERE status = 'failed';"Check logs for API errors:
docker compose logs reddit-capi | grep "Reddit API error"Common errors:
| Error | Cause | Solution |
|---|---|---|
| "unexpected type number" | Value sent as decimal | Verify multiplication by 100 in transformer |
| "unknown field event_id" | Invalid field in payload | Remove event_id from Reddit event object |
| 401 Unauthorized | Invalid access token | Regenerate token in Reddit Events Manager |
| 429 Too Many Requests | Rate limiting | Handled automatically with retry logic |
# Check if secret matches
docker compose exec reddit-capi printenv SHOPIFY_WEBHOOK_SECRET
# Update .env and restart
docker compose restart reddit-capi# Check PostgreSQL status
docker compose ps postgres
# Test connection
docker compose exec postgres pg_isready -U reddit_capiCREATE TABLE events (
event_id VARCHAR(255) PRIMARY KEY,
event_type VARCHAR(50) NOT NULL,
shopify_id VARCHAR(255) NOT NULL,
shopify_payload JSONB,
reddit_payload JSONB,
reddit_response JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
sent_at TIMESTAMP,
status VARCHAR(20) DEFAULT 'pending',
error_message TEXT
);- HMAC Verification: Every webhook validated with SHA-256 HMAC
- HTTPS Only: TLS 1.2/1.3 encryption
- Localhost Binding: Container only accessible via reverse proxy
- PII Hashing: Email/phone SHA-256 hashed before sending to Reddit
- Environment Secrets: All sensitive data in
.env(git-ignored)
The implementation automatically handles different currencies. For currencies with non-standard decimal places:
// Zero-decimal currencies (JPY, KRW): multiply by 1
// Standard currencies (USD, EUR): multiply by 100
// Three-decimal currencies (BHD, KWD): multiply by 1000
value: Math.round(parseFloat(order.total_price) * 100)Extend src/services/shopifyTransformer.js for more events:
export function transformProductView(product) {
return {
event_at: new Date().toISOString(),
event_type: { tracking_type: 'ViewContent' },
// ... user data
event_metadata: {
conversion_id: `product_${product.id}_${Date.now()}`,
item_id: String(product.id),
},
};
}MIT License - see LICENSE file for details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Test your changes
- Submit a pull request
- Issues: GitHub Issues
- Documentation: Full setup guide
- Reddit API Docs: Reddit Conversions API
Based on production implementation for live Shopify stores. Tested with:
- Reddit Conversions API v2.0
- Shopify API 2025-01
- Docker Compose v2.x
- PostgreSQL 16
- Node.js 18
⭐ If this helped you, consider starring the repo!