Ad Network & Earnings

Enterprise partners can embed live rate comparison tables from Bitcompare's ad network directly on their platforms — and earn a share of the click revenue they generate.

How it works

Bitcompare operates a real-time CPC ad network where financial providers bid to appear in rate comparison tables. Enterprise API partners can serve these same tables on their own sites. Every click tracked back to your integration earns you a share of the CPC revenue.

  • Name
    Attribution
    Description

    Each trackingUrl in the placements response is pre-signed with your consumer identity using an HMAC token. No additional signing or token handling needed — attribution is automatic.

  • Name
    Revenue share
    Description

    You earn a competitive share of the gross CPC on every attributed click. Your rate is fixed at the enterprise plan level.

  • Name
    Settlement
    Description

    Earnings accrue daily and are aggregated monthly. Your Bitcompare account manager initiates payouts on an agreed schedule.

  • Name
    Reporting
    Description

    Full click-level and monthly earnings history in your Pro dashboard, including gross CPC, your earned amount, and payout status.


Fetching placements

The /api/v1/placements/tables endpoint returns a ranked list of provider placements for a given category. Each placement includes a fully-formed, pre-signed trackingUrl ready to render.

Fetch table placements

curl https://api.bitcompare.net/api/v1/placements/tables \
  -H "Authorization: Bearer ck_live_your_enterprise_key" \
  -G \
  --data-urlencode "category=savings" \
  --data-urlencode "coin=bitcoin" \
  --data-urlencode "region=us" \
  --data-urlencode "limit=10"
  • Name
    category
    Type
    string
    Description

    Comparison vertical — e.g. savings, crypto-exchanges, lending.

  • Name
    coin
    Type
    string
    Description

    Coin slug to filter rates by (e.g. bitcoin, ethereum). Optional — omit for category-level placements.

  • Name
    region
    Type
    string
    Description

    ISO 3166-1 alpha-2 country code of the end user (e.g. us, gb, au). Always pass this. See Geo-filtering below.

  • Name
    limit
    Type
    number
    Description

    Maximum placements to return. Defaults to 10.

Each placement in the response includes a trackingUrl of the form:

https://track.bitcompare.net/api/v1/tracking/clicks/{providerId}/{shortCode}?partner={token}

The partner query parameter is an HMAC-signed token embedding your consumer ID, generated server-side for each request using your API key. Do not modify or strip it — the click-tracker verifies the signature to attribute the click to your account for revshare.


Rendering placements

The trackingUrl in each placement is complete and ready to use as a link destination. Render it directly:

Basic React example

async function RateTable({ category, coin, region }) {
  const res = await fetch(
    `https://api.bitcompare.net/api/v1/placements/tables?category=${category}&coin=${coin}&region=${region}&limit=10`,
    { headers: { Authorization: `Bearer ${process.env.BITCOMPARE_API_KEY}` } }
  )
  const { data: placements } = await res.json()

  return (
    <table>
      {placements.map((p) => (
        <tr key={p.provider.id}>
          <td>{p.provider.name}</td>
          <td>{p.rate}</td>
          <td>
            <a href={p.trackingUrl} target="_blank" rel="noopener noreferrer">
              View offer
            </a>
          </td>
        </tr>
      ))}
    </table>
  )
}

Click tracking

While trackingUrl can be used directly, wrapping it in a same-domain redirect on your own origin is recommended for production. This has two benefits:

  • Bot filtering — you can inspect the request server-side and drop known bot traffic before it reaches the click-tracker and consumes your CPC budget.
  • Referrer preservation — privacy browsers strip the Referer header on cross-origin navigations. A same-domain hop preserves it, improving attribution data.

Implementing a same-domain redirect

Cache the placements response server-side (the API caches are 5 minutes). Store the trackingUrl keyed by a short identifier you control, then redirect to it from your own domain:

app/go/[id]/route.ts (Next.js App Router)

import { NextRequest, NextResponse } from 'next/server'

// In-process deduplication — use Redis for multi-instance deployments
const recentClicks = new Map<string, number>()
const DEDUPE_WINDOW_MS = 2_000

export async function GET(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  const isBot = req.nextUrl.searchParams.get('is_bot') === 'true'

  // Drop known bots before they hit the click-tracker
  if (isBot) {
    return new NextResponse(null, { status: 403 })
  }

  // Server-side deduplication — block rapid duplicate clicks
  const now = Date.now()
  const last = recentClicks.get(params.id)
  if (last && now - last < DEDUPE_WINDOW_MS) {
    return new NextResponse(null, { status: 429 })
  }
  recentClicks.set(params.id, now)

  // Look up the pre-signed trackingUrl you stored when fetching placements
  const trackingUrl = await getStoredTrackingUrl(params.id)
  if (!trackingUrl) return new NextResponse(null, { status: 404 })

  // Redirect to Bitcompare's click-tracker — partner token is already in the URL
  return NextResponse.redirect(trackingUrl, 302)
}

hooks/use-tracking-link.ts

// Build a link to your same-domain redirect, passing isBot for server-side filtering
export function useTrackingLink(placementId: string, isBot = false) {
  return `/go/${placementId}?is_bot=${isBot}`
}

Bot detection

Pass an is_bot flag to your redirect endpoint. You can derive this from:

  • User-agent analysis — match known crawler strings server-side
  • Client-side signals — mouse movement, interaction timing, or a bot-detection library (e.g. @fingerprintjs/botd)
  • Honeypot links — invisible links that only bots follow

Sending a beacon (optional)

For first-party analytics that bypass ad blockers, fire a sendBeacon to your own endpoint before navigating:

components/placement-link.tsx

'use client'

function PlacementLink({ href, placementId, children }) {
  function handleClick(e: React.MouseEvent) {
    // Non-blocking — survives page unload
    navigator.sendBeacon('/api/events/track', JSON.stringify({ placementId }))
  }

  return (
    <a href={href} target="_blank" rel="noopener noreferrer" onClick={handleClick}>
      {children}
    </a>
  )
}

Geo-filtering

Always pass region. Providers configure country whitelists and blacklists on their placements — the API enforces these server-side at two levels:

  • Placement targeting — individual placements can target a specific country or be global.
  • Provider rules — providers maintain a country_whitelist and/or country_blacklist. A provider excluded by either rule is silently omitted from the response.

If you omit region, all geo restrictions are bypassed and users in blocked regions may see providers they cannot actually sign up with.

Detect region server-side:

Next.js App Router (Vercel / Cloudflare)

import { headers } from 'next/headers'

function getRegion(): string {
  const h = headers()
  // Vercel sets x-vercel-ip-country; Cloudflare sets cf-ipcountry
  const country =
    h.get('x-vercel-ip-country') ??
    h.get('cf-ipcountry') ??
    h.get('x-country-code')
  return country?.toLowerCase() ?? 'us'
}

If your app uses locale-based routing (e.g. /en-us/...), extract from the locale instead:

// 'en-us' → 'us', 'en-gb' → 'gb'
const region = locale.split('-')[1]?.toLowerCase() ?? 'us'

Earnings dashboard

Once your integration is live, all earnings are visible in your Pro dashboard:

  • All-time stats — total clicks, gross CPC generated, earned, and paid out
  • Monthly breakdown — period, click count, earned amount, and payout status per month
  • Click history — individual click records with CPC, your earnings, country, and category

Getting access

The Ad Network is available on the Enterprise plan. Access is gated per API key — your enterprise key automatically unlocks the placements tables endpoint and click attribution.

To request an enterprise plan:

  1. Log in to pro.bitcompare.net
  2. Go to Dashboard → Earnings
  3. Follow the upgrade prompt, or email enterprise@bitcompare.net directly

FAQ

Do I need to sign the tracking URL or add my own token?

No. The trackingUrl returned in each placement is already signed with your consumer identity. Do not modify or strip the query string — the signature is verified by the click-tracker to attribute the click to your account.

Can I cache the placements response?

Yes. The placements API caches provider data for up to 5 minutes. Caching on your end reduces latency and avoids redundant API calls. The pre-signed trackingUrl values remain valid beyond the cache window — they do not expire.

Why do some regions return fewer results?

Providers configure country whitelists and blacklists. A provider excluded from your user's region is silently omitted from the response. Always pass region so users only see providers available in their country. See Geo-filtering.

What happens if I omit region?

Geo restrictions are bypassed — you may show providers not available in the user's country, leading to a poor experience and unconverted clicks.

Do I need a same-domain redirect?

Not strictly — trackingUrl can be used directly as a link href. A same-domain redirect is recommended in production to filter bots before they reach the click-tracker and to preserve the Referer header for attribution accuracy.

How does deduplication work?

If you implement a same-domain redirect, enforce a short deduplication window (2 seconds) server-side, keyed on the placement ID. This prevents rapid double-clicks from being billed twice. For multi-instance deployments, use Redis rather than in-process memory.

When are earnings paid out?

Monthly, once your account manager marks the period as paid. Payment terms are agreed during onboarding. You can see the status of each period in your earnings dashboard.

What categories are available?

Categories correspond to Bitcompare's comparison verticals — savings, crypto exchanges, lending, and more. Contact your account manager for the full list of active categories.

Was this page helpful?