Track funnel events
Report the steps a referred customer reaches after the click, such as an application, an approval or a funded account, with one signed server-to-server call per step. Each step can pay the affiliate a flat amount and counts toward volume caps.
A funnel event is a named step a referred customer reaches after the click: an application started, an application approved, an account funded. You define the steps on the campaign, and your backend reports each one with a signed postback. Rekomi records who referred the customer, pays the affiliate if the step carries a payout, and shows the whole funnel per affiliate.
Events sit next to sales and leads. They never change your sale, lead or click counts, and they work whether or not a payment gateway is connected.
Plan tier required: Starter or higher. Trialing accounts are always accepted.
Define your events
Open the campaign, go to Commission, and add your steps under Funnel events. Each event has:
- a name (for example
Application approved), - a key, made from the name (
application_approved). The key is what your postback sends, and it never changes. A name with no Latin letters or digits gets a key such asevent_3, shown next to the name, - an optional payout per event, a flat amount in your home currency. Leave it empty for a step you only want to track.
The order of the list is the order of the columns in the funnel report. A campaign can track up to 20 events.
Endpoint
POST /api/tracking/eventProduction: https://api.rekomi.com/api/tracking/event (also available as /api/v1/tracking/event).
Authentication is the same as the S2S sale postback: your API key in Authorization: Bearer rk_live_*, plus an X-Rekomi-Signature header carrying an HMAC-SHA256 of {unix-seconds}.{raw-body} computed with the key's signing secret. Reuse the same signing helper. The timestamp must be within 300 seconds of the current time, and the key needs write access.
Request body
{
"event": "application_approved",
"externalEventId": "app_58213_approved",
"affiliateSlug": "jane-recommends",
"customerId": "cust_58213",
"customerEmail": "user@example.com",
"amountCents": 2500000,
"currency": "USD",
"subId": "newsletter"
}event(required): the key of one of the campaign's events.externalEventId(required): your unique id for this occurrence, up to 128 characters. It is unique across your whole account, so a retry with the same id is safe and always returns the first recording, whatever else the retry sends.affiliateSlug(optional): the affiliate who referred the customer. Send it on the first step. On later steps you can leave it out and send only the customer (see Sending the affiliate once).customerId(optional): your own id for the customer. This is what ties the steps of one customer together.customerEmail(optional): the customer's email, used the same way when you have no customer id.programId(optional): the campaign id. Sent together withaffiliateSlug, it must be that affiliate's campaign. Sent without a slug, it limits the customer lookup to that campaign. When an affiliate promotes more than one of your campaigns, send the link slug of the campaign you mean.amountCents(optional): a value for the step, in minor units ofcurrency, for example a funded amount. It is recorded for your reporting and does not change the payout.currency(optional): ISO 4217 code foramountCentsand forcommissionCentswhen you send one. Defaults to your home currency.commissionCents(optional): what this occurrence pays the affiliate, when your backend decides the amount itself. It replaces the event's configured payout for this one occurrence. Send0to record the step without paying.subId(optional): the channel tag the customer's visit carried.occurredAt(optional): when the step happened (ISO 8601), if you are reporting it late. A value without a time zone is read as UTC. Not in the future, and at most five years back.
Send either affiliateSlug or a customer (customerId or customerEmail) on every call.
Sending the affiliate once
Most backends know the referring affiliate when the customer first arrives, and only know their own customer id afterwards. So Rekomi resolves the affiliate in this order:
affiliateSlug, when you send it.- The newest earlier event with the same
customerId. - The newest earlier event with the same
customerEmail. - A recorded lead with that email, from the last 90 days.
That means a typical integration sends the slug with the first step and a stable customerId with every step:
{ "event": "lead", "externalEventId": "c58213_lead", "affiliateSlug": "jane-recommends", "customerId": "cust_58213" }{ "event": "funded", "externalEventId": "c58213_funded", "customerId": "cust_58213" }Response
A newly recorded event:
{ "ok": true, "eventId": "0b6f...", "conversionId": "c0nv...", "commissionCents": 5000, "overCap": false }conversionIdis set when the step paid the affiliate. The money is an ordinary commission from there on: it appears on the Conversions page as a bonus, waits out the refund hold, and is paid with everything else.overCapistruewhen the affiliate had already reached a volume cap set to hold. The step is still recorded. If it carries a payout,conversionIdpoints at a held commission (statusInvalidated,commissionCents0in this response) that pays once you validate it on the Conversions page.
An event that was already recorded:
{ "ok": true, "deduped": true, "eventId": "0b6f..." }Two things count as already recorded: the same externalEventId, and the same event for the same customer. A customer is approved once, so a second application_approved for the same customerId returns the first one.
Errors
409 external_event_id_reused means the externalEventId was already used for a different event: use one id per step, for example the customer id plus the event key. 401 with an empty body means the key, the signature or the timestamp was refused. 402 plan_tier_required means the account is below Starter and not on a trial. 413 means the body is larger than 8,192 bytes, 429 means you are sending faster than the rate limit allows, and 500 persist_failed means the event could not be saved, so retry it with the same externalEventId. Everything else is a 400 with an error code:
error | Cause |
|---|---|
invalid_body | The body is not valid JSON |
event_required, invalid_event | event is missing, or is not a valid key |
unknown_event | The campaign has no active event with that key |
external_event_id_required, external_event_id_too_long | Missing, or longer than 128 characters |
customer_id_too_long, customer_email_too_long | Longer than 255 and 320 characters |
affiliate_slug_or_customer_required | Neither a slug nor a customer was sent |
affiliate_not_found | The slug matches no affiliate in your account |
affiliate_not_resolved | No slug was sent and no earlier event or lead matches the customer |
affiliate_not_active | The affiliate is not approved |
program_mismatch | programId is not the affiliate's campaign |
no_active_program | The affiliate's campaign is inactive or suspended |
amount_out_of_range, invalid_commission | A negative amount, or one beyond 1,000,000,000,000 minor units |
unsupported_currency | currency is not a supported ISO 4217 code |
occurred_at_in_future, occurred_at_too_old | occurredAt is outside the accepted range |
Example
import crypto from "node:crypto";
const BEARER = process.env.REKOMI_API_KEY!; // rk_live_...
const SIGNING = process.env.REKOMI_SIGNING_SECRET!; // rks_...
async function trackEvent(input: {
event: string;
externalEventId: string;
affiliateSlug?: string;
customerId?: string;
customerEmail?: string;
}) {
const body = JSON.stringify(input); // serialize once: these exact bytes are signed and sent
const t = Math.floor(Date.now() / 1000);
const sig = crypto.createHmac("sha256", SIGNING).update(`${t}.${body}`).digest("hex");
const res = await fetch("https://api.rekomi.com/api/tracking/event", {
method: "POST",
headers: {
Authorization: `Bearer ${BEARER}`,
"X-Rekomi-Signature": `t=${t},sig=${sig}`,
"Content-Type": "application/json",
},
body,
});
if (!res.ok) throw new Error(`Rekomi event failed: ${res.status} ${await res.text()}`);
return res.json();
}Reading events back
- Dashboard: the campaign's Performance tab shows the Funnel table, one row per affiliate and one column per event, with a CSV export.
- API:
GET /api/v1/programs/{programId}/funnelreturns the same report (since,untilandaffiliateIdfilters;format=csvfor the table).GET /api/v1/funnel-eventslists the recorded occurrences (programId,affiliateId,event,since,until,take,skip), andGET /api/v1/funnel-events/export.csvexports them. - Webhooks: every recorded event fires
event.recorded. A step that pays also fires the usualconversion.created.
Good to know
- A paying event's amount is the same for every affiliate. To pay different affiliates different amounts, send
commissionCentson the postback. - Removing an event that already has recorded occurrences archives it: it stops accepting postbacks, and its history stays in the report. Removing an event also removes the volume caps set on it.
- An event's payout is cash. On a campaign that rewards with credit, events can be tracked but cannot carry a payout.
- Events are not refunded through
/api/tracking/refund. To take back the commission of a paid step, deny or adjust its bonus on the Conversions page.
Track leads and signups
Capture a free identified signup (an email tied to the referral) before payment, so a later sale is still credited to the affiliate even if the click cookie is gone. Automatic for Stripe, Paddle, Chargebee, Recurly, and Creem; optional for everything else.
No-code & non-Stripe checkouts
Install the Rekomi tracking script for click attribution on a no-code platform, and record conversions via a native processor, the S2S API, or coupon codes.