No release in over 3 years
A Rails engine for tracking affiliate link clicks with redirect support and monitoring dashboard.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.0
~> 13.0
~> 2.0

Runtime

>= 8.0, < 10
 Project Readme

AffiliateTracker

Click tracking for affiliates working with small e-commerce shops. Track your clicks, add UTM params, prove your value.

Who is this for?

You're an affiliate/influencer who promotes products from small shops (Shoplo, Shoper, WooCommerce, IdoSell, etc.) that don't have their own affiliate system.

You send traffic via newsletters, blogs, or social media — but you have no way to prove how many clicks you actually sent. The shop owner sees some visits in Google Analytics, but can't tell which came from you.

This gem solves that:

  • You track every click on your side (proof for negotiations)
  • Links automatically include UTM parameters (shop sees your traffic in their GA)
  • Optional ref= parameter for shops that support simple referral tracking

Not for: Amazon, eBay, or platforms with existing affiliate programs (they have their own tracking and often prohibit link masking).

Problem

You: "I sent you 500 clicks this month"
Shop: "Google Analytics shows only 200 visits"
You: "..."

Solution

Your email → User clicks → AffiliateTracker counts → Redirect with UTM → Shop sees source
                              ↓
                    You have proof: 500 clicks
                    Shop sees: utm_source=yourname

Used in production

This gem powers affiliate link tracking on Snipe Sale — a promotion aggregator for online shops. All outbound merchant links (web pages and email digests) go through AffiliateTracker to count clicks and append UTM parameters.

Features

  • Click tracking with metadata (shop, campaign, etc.)
  • Automatic UTM parameter injection
  • URL normalization (protocol-less URLs get https:// prepended)
  • Click deduplication (same IP + URL within 5s counted once)
  • IPv4 and IPv6 IP anonymization (GDPR-friendly)
  • Built-in dashboard
  • Rails 8+ / zero configuration

Installation

gem "affiliate_tracker"

Or, for the latest development version:

gem "affiliate_tracker", git: "https://github.com/justi/affiliate_tracker"
rails generate affiliate_tracker:install
rails db:migrate

Usage

affiliate_link helper

<%# Simple link %>
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz sukienkę" %>

<%# With metadata %>
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz sukienkę",
      shop: "modago",
      campaign: "homepage" %>

<%# With CSS classes %>
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz",
      shop: "modago",
      class: "btn btn-primary" %>

<%# Block syntax %>
<%= affiliate_link "https://modago.pl/sukienka", shop: "modago" do %>
  <img src="photo.jpg"> Zobacz ofertę
<% end %>

Generates:

<a href="https://yourapp.com/a/eyJ...?s=abc" target="_blank" rel="noopener">
  Zobacz sukienkę
</a>

affiliate_url helper (URL only)

<a href="<%= affiliate_url 'https://modago.pl/sukienka', shop: 'modago' %>">
  Custom link
</a>

User Tracking

Track which user clicked a link by passing user_id:

<%# On web pages - use Current.user %>
<%= affiliate_link "https://shop.com/product", "Buy Now",
      user_id: Current.user&.id,
      campaign: "homepage" %>

<%# In mailers - pass user explicitly (Current.user not available in background jobs) %>
<%= affiliate_link "https://shop.com/product", "View Deal",
      user_id: @user.id,
      shop_id: @shop.id,
      campaign: "daily_digest" %>

Common tracking parameters:

  • user_id - User who clicked (for attribution)
  • shop_id - Shop identifier
  • promotion_id - Specific promotion
  • campaign - Campaign name (e.g., "daily_digest", "homepage")

In Mailers

<%# app/views/digest_mailer/weekly.html.erb %>
<% @promotions.each do |promo| %>
  <%= affiliate_link promo.shop.website_url, "Zobacz promocję",
        user_id: @user.id,
        shop_id: promo.shop.id,
        promotion_id: promo.id,
        campaign: "weekly_digest" %>
<% end %>

Real Example: Shoplo Store

<%# Just the product URL - ref param added automatically from config %>
<%= affiliate_link "https://demo.shoplo.com/koszulka-bawelniana",
      "Zobacz koszulkę",
      shop: "shoplo-demo",
      campaign: "styczen2025" %>

User clicks → AffiliateTracker counts → Redirects to:

https://demo.shoplo.com/koszulka-bawelniana?ref=partnerJan&utm_source=smartoffers&utm_medium=email&utm_campaign=styczen2025&utm_content=shoplo-demo

The shop sees:

  • ref=partnerJan - from config.ref_param (automatic)
  • UTM params - in Google Analytics

Result

  1. Generates: https://yourapp.com/a/eyJ...?s=abc
  2. On click, redirects to: https://modago.pl/sukienka?utm_source=smartoffers&utm_medium=email&utm_campaign=weekly_digest&utm_content=modago

Configuration

# config/initializers/affiliate_tracker.rb
AffiliateTracker.configure do |config|
  # Your brand name (appears in utm_source)
  config.utm_source = "smartoffers"

  # Default medium
  config.utm_medium = "email"

  # Referral param (adds ?ref=partnerJan to all links)
  config.ref_param = "partnerJan"

  # Dashboard auth
  config.authenticate_dashboard = -> {
    redirect_to main_app.login_path unless current_user&.admin?
  }
end

Tracking Parameters

Parameter Source Example
ref config.ref_param partnerJan
utm_source config.utm_source smartoffers
utm_medium config.utm_medium email
utm_campaign campaign: in helper weekly_digest
utm_content shop: in helper modago

Override defaults per-link:

<%= affiliate_url "https://shop.com",
      utm_source: "newsletter",
      utm_medium: "email",
      campaign: "black_friday" %>

Dashboard

Access at /a/dashboard

Shows:

  • Total clicks
  • Clicks today/this week
  • Top destinations (shops)
  • Recent clicks with metadata

Security

  • Links are signed with HMAC-SHA256 (128-bit truncated signature per RFC 2104)
  • Signature verification uses constant-time comparison (ActiveSupport::SecurityUtils)
  • Invalid or missing signatures result in a 302 redirect to a configurable fallback URL (default: /)
  • Payload is Base64-encoded JSON (not encrypted) — metadata is readable but tamper-proof

Fallback URL

When a user visits a link with an invalid or missing signature (e.g., bots stripping query params), the gem redirects instead of returning an error:

AffiliateTracker.configure do |config|
  # Static URL (default)
  config.fallback_url = "/"

  # Dynamic — receives decoded payload Hash (unverified, treat as untrusted)
  config.fallback_url = ->(payload) {
    slug = payload&.dig("shop")
    slug.present? ? "/shops/#{slug}" : "/"
  }
end

For Shop Owners

Tell your partner shops:

"All my links include UTM parameters. Check Google Analytics → Acquisition → Traffic Sources → filter by utm_source=yourname"

License

MIT