Skip to main content

Paying Affiliates Per Lead

How to reward affiliates for leads they send you — not just sales — using your website's own form.

Written by James Wigdor

What this feature does

Most affiliate programs pay a commission when someone makes a purchase. This feature lets you pay affiliates for something that happens earlier in the funnel: when a visitor they referred fills out a form on your site — a webinar signup, a consultation request, an application, a waitlist, whatever your business runs.

Here's the plain-language version of how it works:

  1. An affiliate shares their link. Rootabl already tracks who clicked it and when, the same way it tracks clicks for sales-based commissions.

  2. A visitor lands on your site and fills out your form — the one on your own website, built with whatever tool you already use (a CRM, a custom form, a form builder, etc.).

  3. Your website tells Rootabl about the new lead. This happens automatically, behind the scenes, through a small piece of code your developer sets up once. You don't manually enter anything.

  4. Rootabl matches the lead to the right affiliate, using the tracking data it already has — the same visitor history it uses for sales attribution. If Rootabl recognizes the visitor as having come from an affiliate's link, it credits that affiliate. If there's no tracked affiliate history for that visitor, the lead is still recorded, just without a payout attached.

  5. You pay affiliates per lead, using whatever commission structure you've set up — either a flat rate for every lead, or different rates depending on where the lead came from (more on this below).

The important thing to understand: Rootabl only credits an affiliate if its own tracking data says that visitor actually came from that affiliate's link. This keeps the system honest and prevents accidental (or intentional) misattribution.

Why this matters

If your business generates revenue from something other than a direct sale — a booked call, a qualified application, a free-trial signup — this lets you extend your affiliate program to reward the activity that actually matters to you, instead of forcing every affiliate relationship to wait for a sale that may happen weeks or months later.

What you'll need

  • A website or CRM that can send a simple, automated web request when someone submits your form (this is a job for a developer — it's not something you'll do by hand in a form builder's UI).

  • A few minutes in your Rootabl dashboard to create a secure key and connect it.

If you don't have a developer on hand, share the Technical implementation section below with whoever manages your website or CRM integrations — it has everything they need.


Choosing how affiliates get paid: Standard vs. Advanced commissions

Once lead commissions are turned on for your business, you'll choose one of two ways to calculate what an affiliate earns per lead. You can find this under Lead Commissions in your Rootabl dashboard.

Standard Commission (All Leads)

This is the simplest option: one flat dollar amount, paid for every lead an affiliate refers, no matter which form or offer it came from.

In the example above, every affiliate earns a flat $10.00 for each lead they refer, and the dashboard shows you a plain-language preview of that rule before you save it.

Advanced Commissions

If you run multiple offers — a webinar, a lead magnet, a consultation form — and want to pay different amounts depending on which one a lead came through, switch to Advanced Commissions. Instead of one flat rate, you turn on the specific Lead Tags you want to pay a commission on and set a dollar amount for each one individually.

In the example above, a lead tagged Summer Webinar pays $7.00, while a lead tagged Welcome Form pays $5.00. If a lead could match more than one enabled tag, it's only paid once, based on the first (highest in the list) matching tag.

Advanced Commissions rely on Lead Tags already existing for your business. These are the same tags you (or your developer) set up under Lead List Tags — each one has a name, a tracking URL, and a permanent ID. These can be found within your campaign on the top of the page listed as "Leads" and then the "Manage Tags" button.

That ID shown for each tag (summer-webinar, welcome-form in the example above) is exactly the tag_id value your developer sends in the API request described below — it's what connects a captured lead to the commission rate you set for that tag. If you switch to Advanced Commissions but a lead comes in with no tag, or with a tag that isn't enabled for a commission, it won't have a payout attached under this structure.


Technical implementation

This section is written for the developer or technical teammate setting up the integration.

Overview

You'll send a POST request to Rootabl from your own server (not from the visitor's browser) every time someone submits your lead form. Rootabl validates the request, matches it to any existing affiliate tracking data for that visitor, and stores the lead.

Step 1: Create a Leads API key

In your business's Integrations dialog in the Rootabl dashboard, generate a new Leads API key (https://app.rootabl.com/businesses?redirect=settings/integrations).

  • The full key is only ever shown once, at the moment you create it — copy it into your secrets manager or environment variables immediately.

  • You can keep multiple active keys at once (useful for staging vs. production, or for rotating keys without downtime) and revoke any of them independently.

Step 2: Mark the integration as connected

After you've saved the key somewhere safe, go back to the setup dialog and select Mark as Connected.

This step is easy to miss, but it matters: the key will not authenticate requests until the integration is marked as connected, even though the key itself was already generated.

Step 3: Choose a stable sender ID

Every lead you send needs an external_id — a stable, permanent identifier from your own system (your CRM's record ID, your form backend's submission ID, etc.).

Use the same, permanent ID every time you refer to that lead. Don't generate a new random ID on each retry — external_id is also your idempotency key (see below), so a randomly-changing ID defeats the safeguard against duplicate leads.

Step 4: Send the capture request

Endpoint:

POST https://myrootabl.com/api/v1/businesses/my-awesome-business/leads

Authentication:

Every request must include your Leads API key as a Bearer token in the Authorization header:

Authorization: Bearer YOUR_LEADS_API_KEY

A few important rules:

  • The key must belong to this exact business. A Sales API key, or a Leads API key from a different business, will not authenticate.

  • Never put the key in the URL or the request body. Rootabl only accepts it from the Authorization header. Sending it any other way is rejected.

  • The URL itself must not carry a query string at all — Rootabl treats any query parameters on this endpoint as an error (see status codes below).

Content type and size:

  • Content-Type: application/json is required.

  • The body is capped at 16 KiB.

Request fields

The request body is strict — any field not on this list is rejected outright, including affiliate_id, tags arrays, tag_ids, form_id, and event_name. You cannot manually assign an affiliate; see Attribution rules below for why.

Field

Required?

Type & meaning

external_id

Required

Stable ID from your system, 1–255 characters. This is also the idempotency key.

email

Required

A valid email address, up to 320 characters.

created_at

Required

The lead's original creation time, as an ISO 8601 timestamp with an offset — e.g. 2026-07-28T10:15:00Z.

customer_name

Optional

The lead's name, 1–255 characters.

visitor_id

Optional

The Rootabl visitor UUID from this business's own tracking context (this is what allows Rootabl to attribute the lead to an affiliate).

campaign_id

Optional

(Required if tag_id is used) A Rootabl campaign ID belonging to this business. Rootabl never infers a campaign on its own — if you omit this, no campaign is attached. Required if tag_id refers to a tag that doesn't exist yet.

tag_id

Optional

(Required if campaign_id is used) A lead-tag ID, copied from the campaign dashboard (the same ID shown in the Lead List Tags modal — see the commission section above). If the ID doesn't exist yet, Rootabl creates it automatically — but that requires campaign_id to be present, so the new tag has a campaign to belong to. New tag IDs must be lowercase slugs, 2–48 characters (letters, numbers, dashes, underscores). Existing tag IDs work with or without campaign_id, regardless of format. Omit this to leave the lead untagged. Arrays are not accepted — one tag per lead. If the business uses Advanced Commissions, this is also the field that determines which per-tag rate the lead is paid at.

Attribution rules

Attribution is based entirely on data Rootabl already owns — you cannot tell Rootabl who the affiliate is.

  1. Visitor validation. If you send visitor_id, it must exist for this business. Rootabl can only derive an affiliate from that business's own tracked visitor history — it won't accept a visitor ID from another business's tracking.

  2. Campaign validation. If you send campaign_id, it must belong to this same business, and it's attached to the lead. If you leave it out, no campaign is inferred.

  3. Tag resolution. If you send tag_id, it must identify an active tag owned by this business (and, if campaign_id is also present, the tag must belong to that campaign). An unknown tag_id is created automatically — which is why it requires campaign_id — so double-check spelling before you send it. A typo doesn't error out; it silently creates a brand-new tag. You can rename a tag and add a tracking URL to it later from the dashboard, but its ID is permanent.

  4. Unassigned captures are still valid. If a request doesn't resolve to any affiliate (no matching visitor history), it's still accepted as a valid, unassigned lead — it just won't have a payout attached.

Idempotency

Rootabl stores external_id on the captured lead and enforces uniqueness within your business. If you (or your retry logic) send the same external_id again — even after rotating your API key — Rootabl recognizes it and does not create a duplicate lead.

Two nuances worth knowing:

  • The first accepted request wins. Retrying an existing external_id will not add, remove, or change its tag_id, even if the retry's payload has a different one.

  • Deleting a lead deletes its retry identity too. If you delete a lead and then send that same external_id again later, it's treated as a brand-new lead.

Responses

Success:

Status

Meaning

201 Created

A new lead was accepted.

200 OK

This external_id was already accepted previously — no duplicate was created.

json

// New capture {"status":"accepted","operation":"created","external_id":"crm-lead-123"}  // Existing external_id (retry) {"status":"accepted","operation":"existing","external_id":"crm-lead-123"}

Errors:

Status

Meaning

400

The body isn't valid JSON, or the URL has a query string.

401

The Bearer credential is missing or invalid.

413

The request body exceeds 16 KiB.

415

The Content-Type header isn't application/json.

422

A field is invalid, unknown, or references another business.

503

The capture couldn't be accepted right now — safe to retry.

Every error uses one consistent envelope — an error object with a stable code and a human-readable message. Validation failures (422) also include a fields array pointing to the specific problem:

Validation Error Example

{   "error": {     "code": "validation_error",     "message": "The lead payload is invalid.",     "fields": [       {         "field": "campaign_id",         "message": "campaign_id is required to create a new tag_id"       }     ]   } }

Note: authentication errors (401) are intentionally vague — they won't reveal whether the key, the business, or the integration exists, as a security precaution.

Code examples

Replace YOUR_LEADS_API_KEY with your actual key. The endpoint below is specific to our test example of 'my-awesome-business' — Be sure to copy the correct URL from your own business's own Integrations dialog.

cURL

curl --request POST "https://myrootabl.com/api/v1/businesses/my-awesome-business/leads" \   --header "Authorization: Bearer YOUR_LEADS_API_KEY" \   --header "Content-Type: application/json" \   --data '{     "external_id": "crm-lead-123",     "email": "[email protected]",     "created_at": "2026-07-28T10:15:00Z",     "customer_name": "Ada Lovelace",     "campaign_id": "biz123-ab4Cd",     "tag_id": "webinar"   }'

JavaScript

const response = await fetch("https://myrootabl.com/api/v1/businesses/my-awesome-business/leads", {   method: "POST",   headers: {     Authorization: "Bearer YOUR_LEADS_API_KEY",     "Content-Type": "application/json",   },   body: JSON.stringify({     external_id: "crm-lead-123",     email: "[email protected]",     created_at: "2026-07-28T10:15:00Z",     customer_name: "Ada Lovelace",     campaign_id: "biz123-ab4Cd",     tag_id: "webinar",   }), })  const result = await response.json() if (!response.ok) {   throw new Error(`Rootabl rejected the lead: ${JSON.stringify(result)}`) }  console.log(result)

PHP

<?php  $payload = [     "external_id" => "crm-lead-123",     "email" => "[email protected]",     "created_at" => "2026-07-28T10:15:00Z",     "customer_name" => "Ada Lovelace",     "campaign_id" => "biz123-ab4Cd",     "tag_id" => "webinar", ];  $request = curl_init("https://myrootabl.com/api/v1/businesses/my-awesome-business/leads"); curl_setopt_array($request, [     CURLOPT_POST => true,     CURLOPT_RETURNTRANSFER => true,     CURLOPT_HTTPHEADER => [         "Authorization: Bearer YOUR_LEADS_API_KEY",         "Content-Type: application/json",     ],     CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR), ]);  $body = curl_exec($request); $status = curl_getinfo($request, CURLINFO_RESPONSE_CODE); curl_close($request);  if ($status < 200 || $status >= 300) {     throw new RuntimeException("Rootabl rejected the lead: " . $body); }  $result = json_decode($body, true, flags: JSON_THROW_ON_ERROR);


Common setup mistakes

  • Sending a key before marking the integration "Connected." The key exists but won't authenticate yet — you'll see 401 errors until this step is done.

  • Putting the API key in the URL or body instead of the Authorization header. This is rejected, even though it might seem like a natural place to put it.

  • Trying to pass affiliate_id directly. There's no way to manually assign attribution — the API rejects unknown fields, and affiliate credit only comes from Rootabl's own tracked visitor_id history.

  • Sending a query string on the URL (e.g., ?business=...). The live endpoint doesn't take one — it's baked into the URL path itself. A query string of any kind causes a 400.

  • Reusing a random ID instead of a stable one for external_id. This breaks idempotency and can result in duplicate leads if your retry logic ever fires twice.

  • Typo'ing a tag_id. Since unknown tag IDs are created automatically, a typo silently creates a new (wrong) tag instead of erroring — always double-check the ID copied from the campaign dashboard.

  • Sending the wrong tag_id (or none at all) when using Advanced Commissions. If the business has switched to Advanced Commissions, the tag_id you send determines which per-tag rate the lead is paid at. A missing, misspelled, or not-yet-enabled tag means the lead comes in without a commission attached.

Did this answer your question?