Developers

Tracking API

Revdeo is a managed performance-marketing service with a marketplace: our Revs— together with our marketing team — take over a brand’s social channels and run them performance-driven. The Tracking API is the technical foundation behind it: it ties every conversion deterministically to the Rev who drove it, making ROI cleanly provable.

This page is the self-serve guide for developers whose app does not run on Stripe or Shopify (e.g. Crypto/Web3, fintech, vape, cannabis). You send conversions via server-to-server postback to Revdeo — in five steps below, with copy-paste code.

OpenAPI spec (JSON) →

Gating: Without verified tracking (Stripe OR Shopify OR a received Tracking-API signal), a campaign can only offer pay-per-view (CPM) bounties. Performance bounties (CPA/CPL/Rev-share) are unlocked as soon as the first valid signal arrives.

Quickstart — integrate in 5 steps

1

Get an API key

Open the Builder app → Settings → Tracking and create an API key there. Format rvk_live_. The secret is shown exactly once — store it server-side (e.g. as the env var REVDEO_API_KEY), not in the frontend.

2

Capture rv_ref on the landing page

Every Rev tracking link points to https://revdeo.io/r/<slug>. On click, our redirect appends the parameter rv_ref=<click-id> to your destination URL. Read it, persist it (localStorage + cookie as a fallback), and post it to your backend. Also capture rev_link — paid-ads traffic (e.g. Meta URL tags) lands directly on your page without the redirect, so rv_ref is missing and the slug is your fallback (confidence 95 instead of 100):

<!-- Add to every landing page that receives Rev traffic -->
<script>
  (function () {
    var params = new URLSearchParams(location.search);
    var ref = params.get("rv_ref");
    // rev_link = fallback for traffic that lands directly on your page
    // (e.g. paid ads with URL tags) and therefore has no rv_ref.
    var link = params.get("rev_link");
    if (!ref && !link) return;
    // 1) remember locally in case the conversion happens later
    var maxAge = 60 * 60 * 24 * 90;
    if (ref) {
      localStorage.setItem("rv_ref", ref);
      document.cookie =
        "rv_ref=" + ref + ";path=/;max-age=" + maxAge + ";SameSite=Lax";
    }
    if (link) {
      localStorage.setItem("rev_link", link);
      document.cookie =
        "rev_link=" + link + ";path=/;max-age=" + maxAge + ";SameSite=Lax";
    }
    // 2) post it to your backend to bind it to the session/user
    fetch("/api/attribution", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ rv_ref: ref, rev_link: link }),
      keepalive: true,
    });
  })();
</script>
3

Store rv_ref server-side on the user/lead

When your backend receives the rv_ref, bind it to the identity that converts later: a column on the user/lead record, a field in the server session, or an entry in your orders table. What matters is that you can retrieve the value at the time of conversion (often days later).

4

Send a postback on conversion — server-side

Send every conversion event from your server to https://revdeo.io/api/v1/track, authenticated via Authorization: Bearer rvk_live_. Server-side is mandatory: browser calls can be tampered with and would expose the key. Choose your language:

curl
# Lead (e.g. trial started)
curl -X POST https://revdeo.io/api/v1/track \
  -H "Authorization: Bearer rvk_live_YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "lead",
    "external_id": "lead_8421",
    "click_ref": "<saved rv_ref>",
    "rev_link": "<saved rev_link — fallback when rv_ref is missing>"
  }'

# Sale (with revenue for %-bounties + ROI)
curl -X POST https://revdeo.io/api/v1/track \
  -H "Authorization: Bearer rvk_live_YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "sale",
    "external_id": "order_8421",
    "click_ref": "<saved rv_ref>",
    "amount_cents": 4990,
    "currency": "EUR"
  }'
Node (fetch)
// Node 18+ (global fetch). Never from the browser — the key stays server-side.
const TRACK_URL = "https://revdeo.io/api/v1/track";
const API_KEY = process.env.REVDEO_API_KEY; // "rvk_live_YOUR_SECRET"

async function track(event, externalId, clickRef, extra = {}) {
  const res = await fetch(TRACK_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event,
      external_id: externalId,
      click_ref: clickRef,
      ...extra,
    }),
  });
  if (!res.ok) throw new Error(`Revdeo track failed: ${res.status}`);
  return res.json();
}

// Lead
await track("lead", "lead_8421", savedClickRef);
// Sale
await track("sale", "order_8421", savedClickRef, {
  amount_cents: 4990,
  currency: "EUR",
});
Python (requests)
# Python (requests). Run on your server, not in the client.
import os
import requests

TRACK_URL = "https://revdeo.io/api/v1/track"
API_KEY = os.environ["REVDEO_API_KEY"]  # "rvk_live_YOUR_SECRET"

def track(event, external_id, click_ref, **extra):
    res = requests.post(
        TRACK_URL,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "event": event,
            "external_id": external_id,
            "click_ref": click_ref,
            **extra,
        },
        timeout=10,
    )
    res.raise_for_status()
    return res.json()

# Lead
track("lead", "lead_8421", saved_click_ref)
# Sale
track("sale", "order_8421", saved_click_ref,
      amount_cents=4990, currency="EUR")
PHP (curl)
<?php
// PHP (curl). Server-side — the key must never reach the frontend.
$trackUrl = "https://revdeo.io/api/v1/track";
$apiKey = getenv("REVDEO_API_KEY"); // "rvk_live_YOUR_SECRET"

function rv_track(string $trackUrl, string $apiKey, array $payload): array {
    $ch = curl_init($trackUrl);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer {$apiKey}",
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS => json_encode($payload),
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($status >= 300) throw new RuntimeException("Revdeo track failed: {$status}");
    return json_decode($body, true);
}

// Lead
rv_track($trackUrl, $apiKey, [
    "event" => "lead",
    "external_id" => "lead_8421",
    "click_ref" => $savedClickRef,
]);
// Sale
rv_track($trackUrl, $apiKey, [
    "event" => "sale",
    "external_id" => "order_8421",
    "click_ref" => $savedClickRef,
    "amount_cents" => 4990,
    "currency" => "EUR",
]);

The full list of event names and body fields is in the reference below.

5

Verify

Under Builder app → Settings → Tracking you can see the tracking status. As soon as your first valid signal arrives, it flips to active— and that’s exactly when performance bounties (CPA/CPL/Rev-share) are unlocked. Until then, a campaign can only offer pay-per-view (CPM). Test with a real rv_ref from a Rev link so attribution runs deterministically.

Best practices

Stripe checkout — pass the rv_ref

Already on Stripe? You usually don’t need this server API — two lower-effort options:

// Your server, when creating the Checkout Session — pass the stored rv_ref.
// (You captured + stored it in steps 2-3 above.) That's all Revdeo needs.
const session = await stripe.checkout.sessions.create({
  mode: "subscription",                 // or "payment"
  line_items: [{ price: "price_…", quantity: 1 }],
  success_url: "https://yourapp.com/welcome",
  // rv_ref → resolved to the right Rev on checkout.session.completed (confidence 100):
  client_reference_id: savedRvRef,
});

// Subscriptions: once a subscription is attributed, Revdeo tags it in your Stripe
// account — every future invoice keeps crediting the same Rev automatically.

Reference · The rv_ref flow

Every Rev tracking link points to https://revdeo.io/r/<slug>. On click, Revdeo appends — among others — the parameter rv_ref=<click-id> to the destination URL.

  1. Your landing page reads rv_ref from the URL and stores it on the user/lead.
  2. On the conversion event, you send it back as click_ref to the Tracking API.
  3. With click_ref, attribution is deterministic → confidence 100. Always send it when available.

Reference · Authentication

Per-brand API key, creatable under Builder app → Settings → Tracking. Format rvk_live_. The secret is shown exactly once.

Authorization: Bearer rvk_live_

Reference · Endpoint

POST https://revdeo.io/api/v1/track

Rate limit: 600 requests / 60s per brand.

Body fields

FieldTypeRequiredDescription
eventstringyesEvent type (see event table).
external_idstringyesYour unique ID for this event (idempotency — same ID = no double counting). Max 200 characters.
click_refstringThe rv_ref from the landing URL. For deterministic attribution (confidence 100).
rev_linkstringThe rev_link slug from the landing URL. Fallback when no rv_ref is available — e.g. paid ads (Meta) landing directly on your page with URL tags (confidence 95). Send both when you have them; click_ref wins.
amount_centsintegerRevenue in minor units (4990 = 49.90). For %-bounties and ROI.
currencystringISO-4217, e.g. EUR.
occurred_atstringISO-8601 timestamp. Default: now.
customer_emailstringHashed immediately (sha256); plaintext is not used for attribution — cross-device fallback.

Event types

event (aliases)counted asbounty lever
install, app_install, app_open_firstApp installCPI
registration, signup, register, account_createdRegistrationCPR
lead, trial, trial_startedLeadCPL
sale, purchase, order, depositSale / DepositCPA + % of revenue
subscription, recurring, subscription_invoiceRecurring payment% recurring (within recurringMonths)
subscription_createdSubscription created(no direct lever — value comes via the first invoice)
refund, chargebackRefund(offset, no payout)

Reference · Error codes

StatusMeaning
401API key missing / invalid / revoked
400Invalid body (unknown event, missing external_id, invalid amount_cents/occurred_at)
429Rate limit exceeded — observe the Retry-After header

Reference · Attribution confidence

Every conversion is attributed with a confidence value. Below 50 it lands in the review queue (manual check).

SignalConfidenceSource
cart_attribute100Shopify cart attribute
stripe_metadata100Stripe metadata
server_postback100Tracking API with click_ref
play_referrer100Android Play Install Referrer (deterministic)
mmp_postback95MMP postback — AppsFlyer/Adjust/Branch/Singular
link_param95Tracking API with rev_link (URL-tag fallback, e.g. paid ads landing directly on your page)
promo_code95Discount code → Rev
cookie85First-party cookie
customer_id85Logged-in customer (server-side, ITP-immune)
email_hash80Buyer email hash (unique)
utm70UTM parameter
device_fingerprint65iOS install fingerprint (probabilistic)

This page + the OpenAPI spec are generated from a single source in the code and stay automatically in sync with the tracking engine.