Developers
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.
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.
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>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).
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:
# 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 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). 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
// 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.
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.
click_ref. With a resolved rv_ref, attribution is deterministic (confidence 100). Without it, attribution falls back to cookie/UTM with lower confidence.external_id. Use your own stable ID for the event (order ID, lead ID). The same external_id = no double-counting, even on retries.amount_cents on revenue. Required for %-of-revenue bounties and for ROI reporting. In minor units (4990 = €49.90).Already on Stripe? You usually don’t need this server API — two lower-effort options:
/r redirect adds client_reference_id for you.rv_ref as client_reference_id when you create the Checkout Session:// 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.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.
rv_ref from the URL and stores it on the user/lead.click_ref to the Tracking API.click_ref, attribution is deterministic → confidence 100. Always send it when available.Per-brand API key, creatable under Builder app → Settings → Tracking. Format rvk_live_…. The secret is shown exactly once.
Authorization: Bearer rvk_live_…
POST https://revdeo.io/api/v1/track
Rate limit: 600 requests / 60s per brand.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | yes | Event type (see event table). |
| external_id | string | yes | Your unique ID for this event (idempotency — same ID = no double counting). Max 200 characters. |
| click_ref | string | — | The rv_ref from the landing URL. For deterministic attribution (confidence 100). |
| rev_link | string | — | The 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_cents | integer | — | Revenue in minor units (4990 = 49.90). For %-bounties and ROI. |
| currency | string | — | ISO-4217, e.g. EUR. |
| occurred_at | string | — | ISO-8601 timestamp. Default: now. |
| customer_email | string | — | Hashed immediately (sha256); plaintext is not used for attribution — cross-device fallback. |
| event (aliases) | counted as | bounty lever |
|---|---|---|
| install, app_install, app_open_first | App install | CPI |
| registration, signup, register, account_created | Registration | CPR |
| lead, trial, trial_started | Lead | CPL |
| sale, purchase, order, deposit | Sale / Deposit | CPA + % of revenue |
| subscription, recurring, subscription_invoice | Recurring payment | % recurring (within recurringMonths) |
| subscription_created | Subscription created | (no direct lever — value comes via the first invoice) |
| refund, chargeback | Refund | (offset, no payout) |
| Status | Meaning |
|---|---|
| 401 | API key missing / invalid / revoked |
| 400 | Invalid body (unknown event, missing external_id, invalid amount_cents/occurred_at) |
| 429 | Rate limit exceeded — observe the Retry-After header |
Every conversion is attributed with a confidence value. Below 50 it lands in the review queue (manual check).
| Signal | Confidence | Source |
|---|---|---|
| cart_attribute | 100 | Shopify cart attribute |
| stripe_metadata | 100 | Stripe metadata |
| server_postback | 100 | Tracking API with click_ref |
| play_referrer | 100 | Android Play Install Referrer (deterministic) |
| mmp_postback | 95 | MMP postback — AppsFlyer/Adjust/Branch/Singular |
| link_param | 95 | Tracking API with rev_link (URL-tag fallback, e.g. paid ads landing directly on your page) |
| promo_code | 95 | Discount code → Rev |
| cookie | 85 | First-party cookie |
| customer_id | 85 | Logged-in customer (server-side, ITP-immune) |
| email_hash | 80 | Buyer email hash (unique) |
| utm | 70 | UTM parameter |
| device_fingerprint | 65 | iOS 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.