Rekomi Docs
For developers
Developers

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 as event_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/event

Production: 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 with affiliateSlug, 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 of currency, for example a funded amount. It is recorded for your reporting and does not change the payout.
  • currency (optional): ISO 4217 code for amountCents and for commissionCents when 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. Send 0 to 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:

  1. affiliateSlug, when you send it.
  2. The newest earlier event with the same customerId.
  3. The newest earlier event with the same customerEmail.
  4. 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 }
  • conversionId is 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.
  • overCap is true when the affiliate had already reached a volume cap set to hold. The step is still recorded. If it carries a payout, conversionId points at a held commission (status Invalidated, commissionCents 0 in 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:

errorCause
invalid_bodyThe body is not valid JSON
event_required, invalid_eventevent is missing, or is not a valid key
unknown_eventThe campaign has no active event with that key
external_event_id_required, external_event_id_too_longMissing, or longer than 128 characters
customer_id_too_long, customer_email_too_longLonger than 255 and 320 characters
affiliate_slug_or_customer_requiredNeither a slug nor a customer was sent
affiliate_not_foundThe slug matches no affiliate in your account
affiliate_not_resolvedNo slug was sent and no earlier event or lead matches the customer
affiliate_not_activeThe affiliate is not approved
program_mismatchprogramId is not the affiliate's campaign
no_active_programThe affiliate's campaign is inactive or suspended
amount_out_of_range, invalid_commissionA negative amount, or one beyond 1,000,000,000,000 minor units
unsupported_currencycurrency is not a supported ISO 4217 code
occurred_at_in_future, occurred_at_too_oldoccurredAt 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}/funnel returns the same report (since, until and affiliateId filters; format=csv for the table). GET /api/v1/funnel-events lists the recorded occurrences (programId, affiliateId, event, since, until, take, skip), and GET /api/v1/funnel-events/export.csv exports them.
  • Webhooks: every recorded event fires event.recorded. A step that pays also fires the usual conversion.created.

Good to know

  • A paying event's amount is the same for every affiliate. To pay different affiliates different amounts, send commissionCents on 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.