# Rekomi Documentation (full) Affiliate-program platform for SaaS and ecommerce. Full docs corpus below. --- # Welcome to Rekomi Source: https://rekomi.com/docs > The two-sided affiliate platform for online stores and subscription businesses, and the creators who promote them. --- title: Welcome to Rekomi description: The two-sided affiliate platform for online stores and subscription businesses, and the creators who promote them. --- Rekomi is affiliate tracking software for online stores and subscription businesses. Brands run private affiliate or referral campaigns with native apps for Shopify, Wix, BigCommerce and WooCommerce, native tracking for Stripe, Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo, and Mollie (and any other payment gateway via the server-to-server API), commission tracking, recurring commissions, store credit rewards, and automated payouts. Creators apply to the campaigns they want to promote and manage every one from a single dashboard. These docs cover the full product. Pick the path that matches your role. ## Pick your starting point ### For brands You run an online store (Shopify, Wix, BigCommerce, WooCommerce) or a subscription product (SaaS, AI tool, course, membership). You want to recruit affiliates, track their sales, and pay them commission. - [Rekomi for online stores](/docs/brands/online-stores) is the store owner's guide: install the app, what gets tracked, discount codes, refunds, store credit rewards, payouts and tax. - [Quickstart](/docs/brands/quickstart) gets a subscription business from signup to a working tracking link in about fifteen minutes. - [Campaigns](/docs/brands/campaigns) covers the campaign object end to end. - [Commission models](/docs/brands/commission-models) explains how to structure your payouts. - [Recruit affiliates](/docs/brands/recruit-affiliates) covers the recruitment channels: the public application page, direct invites, and the network. ### For affiliates You have an audience. You want to find campaigns worth promoting, get paid for every customer you bring in, and see your earnings in one place. - [Quickstart](/docs/affiliates/quickstart) walks you through signup, profile, and your first application. - [Browse and apply](/docs/affiliates/browse-and-apply) covers applying to campaigns and the creator network that is launching soon. - [Tracking link](/docs/affiliates/tracking-link) is how you actually drive sales. - [Payouts](/docs/affiliates/payouts) is where the money lands. ### For developers You need to wire Rekomi into your stack (server-side tracking, webhook handlers, custom dashboards). - [API overview](/docs/developers/overview) covers base URLs, auth, errors. - [S2S tracking](/docs/developers/s2s-tracking) covers HMAC-signed sales ingest. - [Webhooks](/docs/developers/webhooks) is for receiving events on your side. ## What you can expect - All data is multi-tenant isolated at the database level (Postgres FORCE ROW LEVEL SECURITY). - Payouts run through Rekomi: you fund each payout run and the money reaches every affiliate in the same run, by Stripe Express, bank deposit or PayPal, with Rekomi as the payer of record for tax forms. - The dashboard and admin surfaces are protected by role-based access control. - Every outbound webhook is signed, so you can verify it really came from Rekomi. - Tax form uploads (W-9, W-8BEN, W-8BEN-E) live in private storage with signed-URL retrieval. ## Getting help - Product questions: chat with us using the messenger in the bottom right of any page. - Security issues: see [security.txt](/.well-known/security.txt), or chat with us. - API status: [status.rekomi.com](https://status.rekomi.com) --- # Brand deals Source: https://rekomi.com/docs/affiliates/bookings > Requests from brands, proposals you send, and deals moving through escrow, in one place. --- title: Brand deals description: Requests from brands, proposals you send, and deals moving through escrow, in one place. --- `/a/bookings` is everything between "a brand noticed you" and "you got paid". Three words to know: - A **request** is what a brand sends through your page's work with me form. - A **proposal** is what you send a brand: your terms, as a one-time link. - A **deal** is accepted terms moving through escrow to payout. ![Brand deals with active deals, an offer to accept, and requests from brands](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-bookings.png?v=1) ## Requests from brands Every submission from your page lands here with the brand's name, contact, budget range, and message; if they picked one of your open booking dates, the request shows that too. New requests are marked until you open them, and you get an email when one arrives. Reply with the **Message** button: the conversation happens on Rekomi, and the brand gets a private chat link by email (they do not need an account to talk to you). You can also jump straight to **Send a proposal** to answer a request with concrete terms, or archive requests that are not a fit. ## Proposals you send A proposal is your terms minted as a one-time link: what you are offering, your rate (in any of 135+ supported currencies), your pitch, and optionally a commission on tracked sales after the flat fee (one-time or recurring). Create one from the button at the top of Brand deals. The composer takes more than just terms: - **Your pitch** is a formatted message (bold, lists, links) shown at the top of the proposal page and inside the proposal email. Links are scanned for safety; only https links survive. - **Brand name** personalizes the page ("Prepared for Acme Inc"). - **Your rate** can be quoted in your own currency; the brand's escrow charge settles in USD at the market rate when they fund, and both amounts appear on the invoice. - **Split into milestones** turns the rate into a schedule (deposit now, the rest on delivery, or one line per deliverable), each with a title, amount, optional description and target date. The brand funds and releases each milestone on its own; see [Deals and getting paid](/docs/affiliates/deals-and-payments#milestones). - **Send to email** is optional. Leave it empty and share the link yourself, or fill it in and Rekomi emails the proposal for you with a View proposal button. - **Terms and review window** (optional): your statement of work, usage rights, exclusivity, and how many days the brand has to review each delivered milestone before it releases automatically (3 to 14, default 7). The brand accepts these by redeeming the link. While a link is still live you can **Edit** it from Proposals you send: price, schedule, terms, pitch. The link and invoice number stay the same and the page shows the new version; if you already emailed it, resend so the brand's inbox copy is not stale. A brand can also answer a proposal with a counter, which arrives under Active deals as an offer you can accept, counter again, or decline. Every proposal also gets its own **invoice number** and a printable invoice page the brand can file, which stays available after they accept and updates its status as the deal is paid and released. The mechanics: - Each link works **once** and expires after **14 days**. - You can have up to **20 live links** at a time; revoke any you no longer want out there. - Emailing a proposal is capped at **5 sends per proposal**, at most one per hour (use Resend email on the row). The recipient is remembered between sends. - When a brand opens the link, they see your pitch, your terms, and the invoice. They create a Rekomi account if they need one, accept, and land directly on funding the escrow. - Once a link is used, the row shows which brand took it. Proposals unlock once your profile is reviewed (the same review that adds the Verified badge). ## Active deals Deals show their live status with what happens next spelled out on the card: | Status | What it means | | --- | --- | | Offered | A brand sent you terms. Accept, decline, or **Propose changes** (different price, a milestone per deliverable, other terms); the brand then accepts, revises, or cancels. Offers expire after 14 days. | | Accepted | Terms locked. The brand funds the escrow next. | | Funded | The brand's payment is settling into escrow (bank transfers take a few days), then you get the green light to deliver. | | Delivered | You marked the work done. The brand reviews and releases; it auto-releases at the end of the review window (7 days unless the deal terms say 3 to 14) if they do nothing. If they request changes, update the work and resubmit. | | Released | Paid out through your payout method, tax forms collected. | Milestone deals show one row per milestone under the card (Not funded, In escrow, Delivered, Changes requested, Approved, Paid), each with its own **Submit as delivered** button. Every deal has its own message thread (the **Messages** button, with unread counts). The money mechanics, fees, and what to do in a dispute are in [Deals and getting paid](/docs/affiliates/deals-and-payments). And when the brand relationships pile up, [Your partners](/docs/affiliates/partners) rolls all of this up into one list per brand. --- # Browse and apply to campaigns Source: https://rekomi.com/docs/affiliates/browse-and-apply > How the curated creator network will work when it opens. --- title: Browse and apply to campaigns description: How the curated creator network will work when it opens. --- The creator network is launching soon. `/a/network` is where you will discover campaigns once it opens; today the page shows a launch notice plus a pointer to your creator profile (edited in the page builder), which you can complete now so you're ready on day one. Until launch, brand invite links and [direct applications](/docs/affiliates/direct-applications) work today. Here's how the network will work. ## What you see Each campaign card shows: - Brand logo and name - Campaign name - Real commission terms: rate, whether it's recurring, and the cookie window - A short description of who the product is for - An instant-approval badge, when the campaign auto-approves - A trust line: whether the brand has a verified payout method and has sent payouts before Filter by brand category using the tabs, or search by brand name. Every campaign listed here belongs to a brand with an active paid plan and a funding source for payouts in place. A brand that loses either drops out of the list until it is restored, so a campaign you can see belongs to a brand that has set up how it pays affiliates. ## Write your pitch Clicking Apply on a card opens the application form. The form asks for your pitch: how would you promote this product, to what audience, with what content. Full campaign terms, the cookie window, and your exact referral tracking link appear on the campaign page once you're approved. Examples that work: - "I would feature this in my newsletter as the 'tool of the week', linking from my homepage and including a short demo video. My subscriber list is 12k product-curious founders." - "I'd record a 5-min YouTube walkthrough for my channel (84k subs, indie dev focus)." Examples that don't work: - "I will tweet about it." - "Let's see how it goes." - "I have a big audience." Specificity gets you approved. Vagueness gets you ghosted. ## Submit Click Apply. You land back on the network browse page, and the campaign shows up under "My applications" with status `Submitted`. Your application goes into the campaign's review queue; you'll get an email when a decision is made. ## Track status `/a/network` has a "My applications" section listing every campaign you applied to. Each row shows: - Campaign name - Application date - Status (`Submitted`, `UnderReview`, `Approved`, `Rejected`) - Rejection reason (if rejected and one was provided) If you get rejected, the reason is visible inline. You can revise your pitch and re-apply. There is no way to withdraw a pending application yourself today; you can simply leave it pending (it costs you nothing), or contact support and we can decline it on our side. ## What happens after approval Your account was already created when you applied, so there is nothing to sign up for. The campaign flips from under review to active in `/a` on its own, and your referral link and stats appear there automatically. It shows up under "Active campaigns", and your tracking link is at `/a/campaigns/{campaign-id}`. We also email you when you are approved; that email has a one-click sign-in if you are not already signed in (single-use, expires in 14 days). If you closed the tab before creating your account, create it at `/sign-up/affiliate` with the same email you applied with and your approval links automatically. See [Tracking link](/docs/affiliates/tracking-link) for what to do with it. ## Why apply through the network instead of direct Three reasons: 1. **Curated**. Every campaign in the network is reviewed and approved by Rekomi before it appears here, and every one of them belongs to a brand with an active paid plan and payout funding in place. 2. **One profile, many applications**. Your profile is reusable across every application. 3. **Status tracking**. You see exactly where every application stands in one view. You can still apply directly via a brand's branded subdomain signup page at `{brand}.rekomi.com/{campaign}` if they share one with you. See [Direct applications](/docs/affiliates/direct-applications). --- # Using creative assets Source: https://rekomi.com/docs/affiliates/creative-assets > Where to find campaign banners and ready-made posts (swipe copy), and how to use them. --- title: Using creative assets description: Where to find campaign banners and ready-made posts (swipe copy), and how to use them. --- Some brands provide ready-made banners and ready-made posts (swipe copy) so you don't have to design your own promotional material from scratch. ## Where to find them Open the campaign's page and look for the "Creatives" section. It only appears if the brand has uploaded assets for that campaign and you are approved on it. If you don't see it, the brand either hasn't added assets yet or you're not approved on the campaign. ## Banners Each banner shows a preview. You can: - Copy the image URL, useful for embedding it directly in a blog post, email, or landing page. - Download the file, useful for posting it natively on social platforms. Pair a banner with your tracking link so clicks on it still get credited to you. ## Ready-made posts Ready-made posts (swipe copy) are ready-to-send text: captions, email blurbs, that kind of thing. Click to copy it in one step. Personalize it before you send it; a ready-made post is a starting point, not something to paste verbatim everywhere. ## See also - [Your tracking link](/docs/affiliates/tracking-link): pair creative assets with your link so activity gets credited to you. --- # Build your creator profile Source: https://rekomi.com/docs/affiliates/creator-profile > The profile that reviewers see when evaluating your applications. --- title: Build your creator profile description: The profile that reviewers see when evaluating your applications. --- Your creator profile is the pitch you make with every application. A complete profile gets you approved into campaigns you would otherwise miss. ## Where to edit Everything is edited in one place: the page builder at `/a/page/design` (Profile tab). Your name, bio, topics, verified socials, and services live there once, and they front both your campaign applications and your [public page](/docs/affiliates/your-page). `/a/network` and `/a/settings` both link you straight to it. ![The page builder's Profile tab: topics, links, and social rows with Verified badges and Verify buttons, with the live preview](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-builder-profile.png?v=2) ## Fields Your bio, niche, and payout country are required to complete setup; the rest is optional, but every extra field improves your approval odds. ### Bio (required) Two or three sentences about what you do and who you reach. Concrete and specific beats generic. Bad: "I create content about technology." Good: "Indie hacker building B2B SaaS. I publish twice-weekly to 12,000 Twitter followers and 4,000 newsletter subscribers, mostly early-stage founders." ### Niche (required) Describe the topic area you cover in your own words; during setup you can also pick from quick suggestions like Indie SaaS, AI tools, Dev tools, No-code, Marketing, Design, Productivity, Newsletters, Courses, Creator economy, E-commerce, or Fintech. Reviewers use your niche to judge fit with the brand's audience, so picking accurately matters. ### Verified socials (your audience proof) There is no "audience size" box to type a number into. Instead, connect your social accounts from your Creator page and your profile shows live, verified follower counts. A verified 10,000-subscriber newsletter or channel carries far more weight with reviewers than any self-reported number, and brands can see the checkmark that proves it. ### Recent results (recommended) A short bullet list of recent wins. Examples: - "Newsletter sponsored by Notion: 4.2% CTR" - "YouTube video on AI tools: 280k views in 30 days" - "Twitter thread on solopreneur SaaS: 1.4M impressions" Real numbers and recent dates work. Vague hype hurts. ### Name, website, and social links You can also add your name and your website; your website doubles as the portfolio link brands click. Social accounts are connected, not typed: hit Verify on a platform row and the account appears with a checkmark once you approve the connection. ## What reviewers see When you apply to a campaign, reviewers see your profile (every field above plus your application pitch). They prioritize: 1. Niche alignment with the brand's target audience 2. Verified audience in the right range for the offer 3. Channel relevance (a video product wants a YouTuber, not a newsletter) 4. Portfolio quality 5. Recent activity (someone who posted last week vs. last year) A clean, honest profile beats an inflated one. Keep your numbers honest; misrepresentation can get your network account suspended. ## Updating your profile Click Save and your changes apply immediately. Reviewers see your current profile, so updates you make while an application is pending are visible immediately; only your pitch is fixed at submission time. ## Privacy Your applications, pitches, and account details are private: they are reviewed by the Rekomi network team and shared with brands only as part of your applications. The profile fields themselves (bio, topics, socials, audience) are private too, until you publish a [public page](/docs/affiliates/your-page). A published page puts those fields on `rekomi.com/@you`, which is public and indexed by search engines; that is the point of the page. If you never publish a page, nothing about you is public. --- # Affiliate dashboard tour Source: https://rekomi.com/docs/affiliates/dashboard > What every page in /a does. --- title: Affiliate dashboard tour description: What every page in /a does. --- Your affiliate dashboard is at `/a`. Here is what each page is for. ## /a (Home) ![The affiliate home page with four KPI cards above the campaigns list](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-home.png?v=4) The landing page. Four KPI cards across the top: - **Earnings to date**: total commission earned across all campaigns, with paid and in-transit amounts underneath. Money tiles show amounts in the currency they were earned in; when your campaigns paid you in more than one currency, each currency is listed on its own rather than added together. - **Active campaigns**: how many campaigns you are currently active in. - **Pending payouts**: commission earned but not yet paid out, with any amount due now underneath. - **Conversions**: total conversions credited to you, with your conversion rate or click count underneath. Below the KPI cards: your list of campaigns. Each row shows the campaign name, brand, your referral link name, clicks, sales, and your status (approved, pending, rejected, or paused). Click any row to go to the campaign detail. ## /a/network The open network is launching soon. Today `/a/network` shows your applications and points you to the page builder, where your one creator profile (bio, topics, audience, socials) is edited; see [Build your creator profile](/docs/affiliates/creator-profile). Once the network opens, this is where you'll browse campaigns and track your applications. See [Browse and apply](/docs/affiliates/browse-and-apply). ## /a/campaigns/[id] ![A campaign detail page for a campaign that pays per sale, per lead and per click: the leg chips under the title, the KPI cards, and the Rekomi tracking link with the earn sentence](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-campaign-detail.png?v=6) Per-campaign detail page. For one specific campaign you are approved into: - Your copy-ready affiliate link: the brand's destination URL with your link name already appended, with a copy button. A campaign that pays per click gives you a Rekomi tracking link (`rekomi.link/your-link-name`) instead, so the click is counted independently before the visitor reaches the brand; a campaign that pays per lead alongside other things also shows a lead-form link below it (your referral link name is the same across every campaign; on sale-model campaigns you can also create extra product and page links here, see [Tracking link](/docs/affiliates/tracking-link)) - Your status, the commission terms, and a cookie-window badge. When a campaign pays for more than one thing (sales, leads, clicks), a chip per leg sits under the campaign name and the link section reads the whole deal out, for example "20% per sale, every renewal, plus $5.00 per qualified lead, plus $0.10 per validated click" - Your coupon codes, if the brand has issued you any or lets you create your own. Some coupons offer a choice of how your code works, for example give your audience the full 10% off and earn nothing, or give 5% off and earn 5% per sale; you pick when you create the code, and each code shows what it pays you. On subscription products the code's rate applies to the payments it discounts. To switch, delete the code and create a new one. - A recruit section, when the brand enables sub-affiliate recruiting - Creatives: the brand's approved banners and ready-made posts (swipe copy), when they've made any visible to you - A performance chart and your earnings on this campaign, shown in the currency they were earned in (per currency when the campaign paid you in more than one). The earnings table opens on All; Sales, Leads and Clicks filters appear above it when the campaign has any of each, and the one you pick is remembered in your browser. A lead or click that does not pay on its own shows a muted Recorded status. - A **Leaderboard** of the campaign's top earners, when the brand has turned it on: other affiliates' names are masked, and your own row shows your name and rank even when you are outside the top 10. Campaigns without it simply don't show the section. - Promotion tips ![A campaign leaderboard with masked affiliate names and your own row highlighted with your rank](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-campaign-leaderboard.png?v=1) This is where you spend most of your time once you have running campaigns. ## /a/performance ![The Performance page with the earnings ledger and analytics breakdowns](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-performance.png?v=5) Your earnings ledger plus analytics, in one place. (`/a/earnings` and `/a/insights` redirect here.) The ledger lists every conversion credited to you across every campaign. Columns: - Date - Type (new customer, recurring, or one-time; clicks, leads, and bonuses appear here too where they apply) - Sale amount - Commission - Status (pending, owed, paid, refunded, or Recorded for a lead or click that does not pay on its own) The ledger opens on All. Sales, Leads and Clicks filters appear above it when you have any of each, and the filter you pick is remembered in your browser and shared with the campaign pages. Sort any column. The table pages 50 rows at a time and covers your recent history; export the history as CSV (the export follows the filter you have picked). The analytics side gives you charts and breakdowns: earnings per click, top links (including per-product and per-page deep links, with their own clicks and sales), geography, devices, and more. When your sales came in more than one currency, the breakdowns and their CSV downloads list each currency on its own instead of adding them together, and the CSVs carry a currency column plus per-currency totals. The ledger and the payouts page always show native amounts, the currency each commission was earned in. ## /a/payouts The full payout history. Every time you have been paid out (across all your campaigns). Columns: - Date - Period (start to end of the payout window) - Method - Status (paid, in transit, failed) - Amount (net of the payout fee, which is noted under the amount) If a payout failed, the failure reason is shown. ## /a/settings Where you manage your profile, referral link name, payout method, and tax details. Sections: - **Profile photo** - **Network profile**: bio, niche, audience, and social links. - **Referral link**: your link name, with a one-time change. - **Payout method**: pick your payout country; Rekomi routes you to the right payout method. One method per person, so there is no separate default-method picker. - **Tax form**: for Stripe payouts, tax details are collected by Stripe during onboarding, so there is nothing to upload. Affiliates on the PayPal and bank-deposit payout methods upload a signed W-8 PDF here. - **Account**: add a brand role to the same login. ## Navigation The sidebar has Campaigns (`/a` home), Performance, Payouts, Recruit, Network (coming soon), and **Account settings** (also behind your avatar menu). The header has a notification bell with your unread count; clicking a notification opens the related page and marks it read. There is no organization switcher on the affiliate side; if you also run a brand workspace, the **Switch to Brand Account** action in your avatar menu swaps between the brand and affiliate sides. ## What's not in /a - Brand-side operations: approving affiliates, running payouts, API key management, webhook subscriptions, and team management all live in the brand dashboard. - Your own charts and breakdowns are not missing; they live on the Performance page. If you ever want to operate as a brand (run your own campaign, pay other affiliates), you can. Sign up at `/sign-up` and pick "I run a subscription business". You'll be routed to `/sign-up/plan` to pick a tier and start a 14-day free trial with a card on file. Your same login will join the new organization. --- # Deals and getting paid Source: https://rekomi.com/docs/affiliates/deals-and-payments > How escrow protects you, when to deliver, and exactly what lands in your account. --- title: Deals and getting paid description: How escrow protects you, when to deliver, and exactly what lands in your account. --- Every flat-fee booking on Rekomi runs through escrow: the brand pays up front, Rekomi holds the money, and it is released to you when the work is delivered. You never start work on a promise. ## The life of a deal 1. **Terms are agreed.** Either a brand sends you an offer (accept, decline, or propose changes within 14 days) or a brand accepts your [proposal link](/docs/affiliates/bookings) (or counters it, which comes back to you as an offer). Terms can go back and forth until one side accepts; nothing is charged before that. Deals are $50 to $50,000 (USD equivalent; you can quote in any of 135+ supported currencies), optionally with a commission tail on tracked sales after the flat fee. 2. **The brand funds the escrow.** They pay the full amount (plus a capped service fee on their side; every Rekomi fee is brand-side, see [Fees](/docs/affiliates/fees)) by bank transfer in USD; a deal quoted in another currency converts at the market exchange rate when the brand funds, and the locked rate appears on the invoice. Bank transfers take a few business days to settle; your deal card says "settling" until it lands. 3. **You get the green light.** When the money is secured, the deal thread posts "Funds are in escrow with Rekomi. Safe to deliver the work." and you get an email. Until then, hold off; this is the whole point of escrow. 4. **Deliver, then mark it delivered.** Do the work, then hit **Mark delivered** on the deal. The brand is notified to review and release. 5. **You get paid.** The brand releases the payment, and it goes out through your payout method like any Rekomi payout. If the brand does nothing, the payment **releases automatically at the end of the review window** (7 days after delivery unless the deal terms set 3 to 14 days); we also remind them at day 3. A silent brand can never hold your money. ## Milestones A deal can be split into milestones (a deposit up front, the rest on delivery, or one payment per deliverable). You set the schedule when you create a proposal (tick **Split into milestones**; the default template is 50% deposit, 50% on delivery), or a brand sets it on their offer. Each milestone then runs the steps above on its own: the brand funds it into escrow, you get the green light for that milestone, you **Submit as delivered** when its work is done, and the brand releases it (or it auto-releases when the review window ends). The brand can also **request changes** on a submitted milestone, which pauses its auto-release clock until you resubmit; you see what they asked for in the deal thread. Every milestone pays out through your payout method separately, and each has its own invoice under the proposal number. ## Proof of delivery, disputes, and changes - **Submitting a milestone** asks for links to the delivered content, an optional note, and a checkbox confirming the sponsorship disclosure is in place (some brands require it). The brand sees all of it on the milestone. - **Results on the milestone**: you can add the numbers the content did (views, likes, comments). If a proof link is a video on your own verified YouTube channel, Rekomi pulls the video's public numbers automatically and refreshes them daily for 30 days; those show to the brand as "verified via YouTube". Numbers you type yourself always show as "creator-reported, unverified", so keep them honest. - **The brief**: a deal can carry a structured brief (the one-liner, full description, call to action, promo link, assets, and the accounts to tag or mention in your content). Both sides see it on the deal page; the brand can edit it only until the first milestone is funded, after which it freezes, so the goalposts cannot move once money is in escrow. - **Terms** on the deal (statement of work, usage rights, exclusivity, and the brand's review window of 3 to 14 days) are shown on the card; accepting records your agreement with a timestamp. You can set your own terms and review window when you create a proposal. - **Disputes**: if a brand goes quiet after delivery, or you disagree about a milestone, open a dispute on it from the deal card: reason, what happened, evidence links, and the outcome you propose (release the full amount to you, a split, or a refund to the brand). The money stays in escrow while it is open. The brand has 7 days to accept your proposal or disagree; if you cannot agree, Rekomi reviews both sides and applies a decision. A brand can open a dispute the same way, and you get the same 7 days to respond. - **Changing the schedule**: propose a change to an unfunded milestone (title, amount, date) or add one; the brand accepts or declines and nothing changes until they do. ## What lands in your account Rekomi takes nothing from your side, on any deal. The only deduction is the payment processor's own cost, passed through at cost: | Your payout method | A $1,500 deal pays you | | --- | --- | | Stripe (bank payout) | $1,500 minus $2.25 (once per calendar month) minus 0.25% ($3.75), so $1,494.00 | Deal releases pay out through Stripe only. If your payout method is PayPal, add a Stripe payout account under `/a/settings` before the brand releases; the deal thread tells you if a release is waiting on that. PayPal stays available for affiliate commissions, where the payout is dispatched automatically and usually arrives within minutes. The complete schedule, including what the brand pays, is in [Fees](/docs/affiliates/fees). ## Holding periods and what unblocks a release - **New-account holding period.** When the brand is not yet verified by Rekomi, or your own payout account is under a week old, a released payment waits 7 days before it goes out. The deal shows the exact date and it pays automatically; you do not need to do anything. Rekomi may cancel a payment during that window if the brand's funding turns out to be bad, and you are told in the deal thread. - **Tax form and payout setup.** A release only goes through once your W-9 or W-8 is on file and your payout account is finished (`/a/settings` under payouts). If either is missing when the brand releases, the deal thread says the payment is waiting on you, and it goes out as soon as you finish. - **Funding under review.** Sometimes Rekomi takes a quick look at a new brand before its first deal is funded. The deal shows "Under review" and nothing is charged yet; it usually clears within a business day. Do not start the work until the escrow reads "In escrow". ## If something goes wrong - **The brand disputes the delivery**: talk it out in the deal's message thread first; most issues are a revision, not a standoff. If you cannot resolve it together, email [support@rekomi.com](mailto:support@rekomi.com) and we will step in. The money stays safely in escrow meanwhile. - **The brand's funding fails**: the deal steps back to Accepted and the brand is told to fix their bank and fund again. Nothing is lost; just wait for the green light before delivering. - **Your payout fails** (payout account issue): the brand sees it and can retry the release once your payout setup is fixed. Check `/a/settings` under payouts. ## Commission tails A deal can include a commission on tracked sales after the flat fee: a percentage, one-time or recurring for a set number of months. Accepting a deal with a tail automatically sets up your tracking link in the brand's campaign; the tail then earns like any campaign commission and pays out through the normal [payout flow](/docs/affiliates/payouts). --- # Product & page links Source: https://rekomi.com/docs/affiliates/deep-links > Link directly to a specific product or page on the brand's site, beyond your referral link. --- title: Product & page links description: Link directly to a specific product or page on the brand's site, beyond your referral link. --- Your referral link is your all-purpose signup link: it works on any page of the brand's site. On top of that, most campaigns let you create product & page links (the industry calls these deep links): direct links to a specific product or page on the brand's site that still carry your own tracking code. Focused links convert better than a homepage link, because people land on the exact thing you were talking about. ## Your referral link Your referral link is always available, works on any page of the brand's site, and is your default: use it when you don't need to point at anything specific. ## Creating a product or page link On sale campaigns, you can create up to 20 links per campaign, including your referral link, from the Product & page links section of the campaign page. Each one is a direct link to a specific page on the brand's site, carrying your link's own code; there's no redirect step in between. - If the brand runs on Shopify, a product search lets you pick a product and the destination fills in automatically. The product picker shows each product's rate as a chip: "Earns X%" when the brand set a product-specific rate for the campaign, otherwise the campaign rate, so you can see what a product pays before creating the link. - Otherwise, paste any URL on the campaign's website. It must be on the brand's own site; you can't point a link at an unrelated page. - Pick a custom code if you want a readable link (3 to 64 letters, numbers or hyphens, unique across Rekomi), or leave it empty for a generated one. If what you want is to tell your channels apart rather than your pages, you do not need extra links at all: add a tag like `&sub=yt` to your one link and Rekomi reports each tag on its own. See [Sub-IDs](/docs/affiliates/tracking-link#sub-ids). Whichever link a customer clicks, your commission follows what they actually buy. If the brand pays a different rate on certain products, that rate applies based on the purchase, not on which link you shared. Brands choose which products appear in the picker, so a product you can't find was likely not made available; you can still link to any page on the brand's site by pasting its URL. For a quick one-off, the campaign page's "Make a link to a specific page" helper appends your link name to any page URL right in your browser, no save step needed; if you want per-page stats later, create a proper Product & page link instead. ## Tracking Each product or page link tracks its own clicks and sales separately from your referral link and your other links. Sales are attributed to the specific link whose code the customer carried; this works across the billing providers that attribute by link code (Stripe, Paddle, Lemon Squeezy, Chargebee, Polar, Recurly, Braintree, Gumroad, Creem, Dodo Payments, Mollie, and server-to-server tracking). On Shopify stores it works with nothing to install: the Rekomi app watches for your link on the storefront and also reads the order's own journey data recorded by Shopify, so a buyer who clicks your link and orders credits that exact link even if their browser blocks trackers. Sales made with your [coupon code](/docs/affiliates/dashboard) credit your account as a whole rather than any single link. ## Deleting a link A link with recorded sales can't be deleted: it's part of your earnings history. The same goes for a link with paid clicks on a pay-per-click campaign. A link with only raw clicks and no sales (or no paid clicks) can be deleted; deleting it removes that link's click history. ## Pay-per-click and pay-per-lead campaigns - Pay-per-click campaigns keep their rekomi.link short links. - Pay-per-lead campaigns use one hosted lead form link instead of short links. ## See also - [Your tracking link](/docs/affiliates/tracking-link): the underlying link name and cookie mechanics that these links build on. --- # Apply directly via a brand's public page Source: https://rekomi.com/docs/affiliates/direct-applications > Use a brand's branded subdomain signup URL to apply outside the network. --- title: Apply directly via a brand's public page description: "Use a brand's branded subdomain signup URL to apply outside the network." --- Some brands run public application pages. The URL looks like: ``` https://{brand}.rekomi.com/{campaign-slug} ``` A brand might link to this URL from their pricing page footer, share it on Twitter, send it to you directly, or list it on their blog. Anyone with the URL can apply, no Rekomi account required up front. ## What the page shows ![A public campaign application page on a branded subdomain](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/portal-public-page.png?v=2) By default the page shows: - Campaign name - Commission type and value - Cookie window - Attribution model (first touch or last touch) - A short application form Brands can add more and customize the layout: a campaign description, their logo and name, a link to their terms. So a given page may add, reword, or remove sections. ## Apply Fill in the form. The exact fields vary by brand; a typical form asks for: - **Email** (required): where the approval or rejection email goes. - **Full name** (required) - **How you plan to promote** (required): a sentence about where and how you'll share the product; include your site or channel link here if you have one. - **Country** (optional): pre-selects your payout country so account setup is one step shorter. Some brands add extra questions, and some let you request a **custom alias** (optional): 3-64 letters, numbers, or hyphens, used for your referral tracking URL. Leave it blank and Rekomi generates one for you. Submit. The brand gets the application in their dashboard. ## What happens next The moment you submit, Rekomi emails you a 6-digit code. Enter it and you land straight in your dashboard: if you are new, your account is created right there (no password, no sign-up form; you just confirm your payout country if the application didn't ask for it), and if your email already has a Rekomi account, the same code signs you in. Your application attaches to your account automatically by email and appears in `/a` right away. What the campaign does from there depends on whether the brand auto-approves applications. ### Manual review (the default) Your application shows in `/a` as under review while the brand looks at it. Your referral link and stats stay locked until they approve. When they do, the campaign flips to active and your link and stats appear automatically, with nothing extra to click. We also email you the outcome; if rejected, you may get an optional reason. ### Instant approval Some campaigns approve applications instantly. Your referral link is live in `/a` as soon as your account is created. No human review. ## If you close the tab Your application is saved the instant you submit, so nothing is lost. You can create your account any time from [Create your account](/sign-up/affiliate) using the same email you applied with, and your application links automatically (a one-time match on that email). The approval email also carries a one-click sign-in as a backup. Either way, your direct application is treated identically to a network application: it shows up in `/a` under your campaigns. ## Custom alias If you're new to Rekomi and the brand allowed it, the custom alias you pick when applying becomes your account-level referral link name, the one you use everywhere (`jane-recommends` instead of a random `x7y9z2`). If you already have a Rekomi affiliate account, you keep your existing link name; new applications use it automatically. Custom aliases must be: - 3-64 characters - Letters, numbers, or hyphens only (no leading or trailing hyphen) - Globally unique (it's your account identity) If the alias you pick is already taken or invalid, the form shows an error so you can choose another. Leave the field blank and Rekomi generates one for you. You can change your link name once from [Settings](/docs/affiliates/tracking-link). ## Multiple campaigns A direct application is per campaign. If you want to join multiple campaigns (from the same brand or different brands), apply to each. Your Rekomi affiliate account, including your **one referral link name**, is shared across all of them; only the per-campaign approval differs. You use the same `?via=your-name` everywhere. ## Versus the network Direct application gives you no pre-vetting on the brand. Some `{brand}.rekomi.com/{campaign}` signup pages belong to brand-new, unvetted products. If the page links to the brand's terms, read them before applying; if there is no terms link, ask the brand for their terms first. If anything feels off (commission structure that punishes long-tail revenue, no support link, sketchy domain), skip. --- # Earnings and performance Source: https://rekomi.com/docs/affiliates/earnings > Your conversions ledger, refund handling, and what each status means. --- title: Earnings and performance description: Your conversions ledger, refund handling, and what each status means. --- Your earnings live on `/a/performance`, the Performance page that combines your conversions ledger with your analytics: a trend chart, summary cards, the full ledger of every commission credited to you across every campaign, and breakdowns by campaign. Older `/a/earnings` links redirect there automatically. It is the ground truth for "what have I earned and what is it doing right now". ## What you see ![The affiliate Performance page with the earnings trend chart, summary cards, and the conversions ledger](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-performance.png?v=5) The conversions ledger has columns: - **Date**: when the conversion was recorded. - **Type**: what kind of conversion it is. **New customer** for a customer's first subscription payment, **Recurring** for renewals, **One-time** for single purchases, and **Click**, **Lead**, or **Bonus** for rewards that are not sales. - **Amount**: what the customer paid the brand (in the brand's currency). - **Commission**: what you earned on this conversion. - **Status**: the current state of the commission (see below). Click any column header to sort, including Status, which groups rows by state. The ledger opens on All. Sales, Leads and Clicks filters appear above it once you have any of each, and the filter you pick is remembered in your browser; each campaign page's earnings table shares the same choice. The ledger shows your most recent conversions, 50 per page. For your complete history, use Export CSV (see below). Some brands also turn on a commission-earned email: when a commission lands, you get an email about it, capped at one per day so a busy day never floods your inbox. It is off unless the brand enables it, so no email never means no commission; this ledger is always the current truth. ## Status meanings A conversion moves through these states (the refund cases apply to sales, since a lead or click has no invoice to refund): ### Pending The sale was credited. It sits in the refund-hold window so the brand can claw back the commission if the customer refunds their first invoice. Default hold is 30 days from the invoice paid date. You see the sale but cannot withdraw the commission yet. ### Approved The brand reviewed and confirmed the sale by hand. You do not need to see Approved to get paid: once the hold window passes, a Pending sale becomes payable automatically and is included in the next payout the brand runs. On a campaign page, an approved commission that is waiting for a payout shows as Owed. ### Paid The payout settled. The money is in your Stripe Connect account (or your PayPal, or your bank account, depending on payout method). You can spend it. You keep 100% of your commission. The amount that lands is your commission net of a small standard withdrawal fee, a flat $2.25 plus 0.25%, charged just once per monthly payout. That covers the cost of moving money to you and Rekomi takes no margin on it. See [Payouts](/docs/affiliates/payouts) for the full breakdown. ### Refunded The customer refunded their invoice within the hold window. A full refund reverses the whole commission. A partial refund reverses only its share, proportional to how much of the sale was refunded (a 40% refund subtracts 40% of the commission), and the rest of your commission stays payable; the sale only flips to Refunded once refunds add up to the full invoice. If the commission had already been paid out (rare with the default 30-day hold), the refunded share is owed back: your payouts page shows the amount, and it comes off your next payout automatically, as set out in the [Affiliate Terms](https://rekomi.com/legal/affiliate-terms). The brand can also reverse the paid commission on Stripe right away, in which case nothing more is deducted. The refund date is included in the CSV export. ### Recorded A lead or click that does not pay on its own, either because the campaign does not pay for that leg or because it is a signup marker kept for attribution. It shows a muted Recorded status and never moves to Pending, Owed or Paid. ### Denied Rare. Manually denied by the brand. Typically only happens for disputed sales where the brand has evidence the affiliate did not actually drive the customer (proxy clicks, fraud, terms violation). If you believe a sale was denied in error, ask the brand or contact support. ## Free month rewards Some brands reward referrals with a free month of their product instead of cash. When you are a customer of that brand and a friend you referred pays their first invoice, the brand's billing platform gives you the credit, and it comes off your next invoice with them (on some platforms the next charge is skipped instead). On the campaign page you see Free months earned and Pending credits instead of money, and the ledger shows each referral as a free month with its status: pending, applied, or reversed if the friend's payment was refunded. Nothing about payouts applies to these rewards, so there is no payout method to set up and no tax form to send for them. If a credit is waiting on an account match, make sure the email on your account with the brand is the same one you joined the campaign with. ## How to read the lifecycle For most subscription products on the default settings (30-day hold, per-sale recurring), a typical sales lifecycle: 1. Day 0: customer signs up via your link and pays first invoice. Sale created with `Status = Pending`. 2. Day 0 to 30: customer can refund. If they do, sale flips to `Refunded`. 3. Day 30: if no refund, the sale becomes payable. It stays Pending unless the brand marks it Approved by hand, and either way the brand can include it in a payout batch. 4. Day 30 onwards (whenever the brand runs payouts): you get paid. For recurring subscriptions, this lifecycle repeats per invoice. Customer pays month 2, you get another sale at the recurring rate. Customer pays month 3, another one. And so on. ## Cap and delay settings Brands can configure two settings that affect what you see: - **A time limit per customer**: for example, if the brand sets 12 months, you earn on every payment in that customer's first 12 months from their first payment (12 payments on a monthly plan, one on a yearly plan), then stop. The limit follows the customer, so a cancel and resubscribe does not restart it. The customer keeps paying the brand; you stop earning. - **A skip of the first N invoices**: for example, if set to 1, you only start earning from the customer's second invoice. Brands use this if they have a high first-invoice refund rate. These are brand-side settings; if your recurring commissions stop or start later than expected, ask the brand whether a commission cap or an invoice delay applies. ## What if a conversion is missing If you drove a customer and the conversion is not showing up: 1. **Check the attribution window**. Did the customer convert within the brand's window? The default is 60 days, but brands can set it shorter or longer. 2. **Check attribution model**. If the brand uses LastTouch and another affiliate touched the customer more recently, they get credit. 3. **Check server-to-server (S2S) setup**. If the brand uses server-side tracking (no edge redirect) and they haven't set it up correctly, conversions get dropped. 4. **Ask the brand**. The conversion list at their `/dashboard/conversions` shows every conversion across all affiliates. They can see whether yours was attributed or not. If after all that, the conversion is genuinely missing, chat with us and share the customer's email (or partial email) and the approximate date. We can trace through logs. ## Export Click Export CSV at the top of the page to download your earnings history, including refund dates, for your records or tax reporting. The export follows the filter you have picked on the ledger, so pick All for the complete history. --- # Fees Source: https://rekomi.com/docs/affiliates/fees > The complete fee schedule. Rekomi takes zero profit from creators, ever; here is every number on both sides. --- title: Fees description: The complete fee schedule. Rekomi takes zero profit from creators, ever; here is every number on both sides. --- Rekomi takes zero profit from creators. Ever. No commission cut, no deal fees, no subscription. This page is the complete schedule, including what brands pay, so you can see exactly where every fee lands. ## What you pay Nothing goes to Rekomi. The only deduction you will ever see is the payment processor's own cost of moving money to you, passed through exactly, with nothing added on top. It depends on your payout method: | Payout method | Processor cost (passed through at cost) | | --- | --- | | Stripe Express (41 countries) | $2.25 once per calendar month (only in months you are paid) + 0.25% of the transfer. That is Stripe's base cost; Rekomi adds nothing. If your bank is in another currency, Stripe's own conversion rate applies on top; see [Getting paid > Currency](/docs/affiliates/payouts#currency). | | Bank deposit (60 more countries) | Stripe's per-send cost, passed through exactly: $1.50 per payout plus a cross-border and currency-conversion rate that depends on the destination country (0.75% to 2.25% of the amount sent). Rekomi adds nothing. | | PayPal (31 more countries) | 2% of the amount sent, capped at $20, charged by Rekomi (exactly PayPal's own sending cost). PayPal deducts nothing on arrival; the USD amount lands in full, and converting it to another currency is at PayPal's own rate. | A worked example on the Stripe rail: you earn $500 in commissions this month. Your payout is $500.00 minus $2.25 minus $1.25 (0.25%), so $496.50 lands in your bank, and that $3.50 went to Stripe, not to Rekomi. If the same brand pays you twice in one month, the $2.25 is only charged once. ## Deals Deals follow the same schedule. When a brand books you for a flat amount (a sponsored video, a newsletter placement, anything from your services list or in a proposal link), the brand funds the full amount with Rekomi up front, plus a brand-side service fee (3% with a $5 minimum, capped at $150 per deal; brands that discover you through the Rekomi directory pay a one-time 10% match fee, capped at $500, on their first deal with you only; the cap is per deal even when a deal is paid in milestones). After the brand confirms delivery, you receive the deal amount minus only the processor pass-through above. A $1,500 deal on the Stripe rail pays you $1,500.00 minus $2.25 minus $3.75 (0.25%), so $1,494.00 lands in your bank and Rekomi keeps none of it. The full deal terms are in the [Affiliate Terms](https://rekomi.com/legal/affiliate-terms). ## What brands pay (never you) Full transparency: this is the other side of the schedule. Every row here is paid by the brand, never deducted from you. | Fee | Amount | Paid by | | --- | --- | --- | | Platform subscription | $29, $49, or $99 per month | The brand | | Payout fee (commissions) | A flat 3% on the money they send you | The brand | | Deal fee (booking you directly) | 3% of the deal, minimum $5, capped at $150 per deal | The brand | | One-time match fee (they found you in the directory) | 10% of the first deal only, capped at $500; every deal after that is the capped 3% | The brand | When a brand pays you $1,000 in commissions, the brand is charged about $1,030 and you are credited the full $1,000. Marketplaces typically take 15 to 25% out of creator deals; on Rekomi the brand pays 3% (capped at $150 on deals) and your side stays whole. ## Why it works this way Brands pay for the platform because it grows their revenue. You are the supply that makes it valuable, so your side stays free: your profile, your page, your tracking, and your tax handling are all included at no cost to you. Rekomi only earns when brands move money, which means we are paid for making sure you get paid. Questions about a specific deduction? Check [Getting paid](/docs/affiliates/payouts) for payout timing and statuses, or [Tax forms](/docs/affiliates/tax-forms) for how tax handling works. --- # Messages Source: https://rekomi.com/docs/affiliates/messages > Every brand conversation in one inbox, in real time, without leaving Rekomi. --- title: Messages description: Every brand conversation in one inbox, in real time, without leaving Rekomi. --- `/a/messages` is your inbox: one thread per booking request and per deal, conversations on the left, the chat on the right. It is live chat: messages appear instantly, and you see a typing indicator when the other side is writing. ![The messages inbox with a deal thread: system notes for escrow events and live chat](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-messages.png?v=1) ## How threads work - A thread starts when a brand sends a request through your page, or when a deal is created. - Deal threads carry the deal's history as system notes ("Funds are in escrow", "Payment released"), so the money status and the conversation live in one place. - Unread counts show in the inbox, on your Brand deals cards, and in the sidebar. ## Brands without accounts When a brand contacts you through your page and you reply, they get an email with a private chat link. That link opens a live chat with you, no Rekomi account needed, and it keeps working for the life of the conversation. You will not notice a difference on your side; their messages arrive in the same thread. They can see any attachments you send, but sending files needs a Rekomi account, so guest brands write text only. ## Email notifications You get an email when a new message arrives and you are away. If you are looking at the thread, no email. If more messages arrive while you have not read the first, we stay quiet instead of stacking emails, with at most one reminder every few hours. Reading the thread resets it. The same rules apply to the brand's side. Every notification email links straight to the exact thread. ## Ground rules - Messages are text, up to 4,000 characters, and can carry up to 5 attachments each: PNG, JPG, or WebP images and PDFs, up to 10MB per file. Images show inline; PDFs show as file chips. - Keep conversations on Rekomi. Deals, delivery confirmations, and disputes all rely on the thread being here; it is also what lets us step in and help if something goes wrong (see [Deals and getting paid](/docs/affiliates/deals-and-payments)). - Threads on declined, cancelled, or expired deals close for new messages; the history stays readable. --- # The page builder Source: https://rekomi.com/docs/affiliates/page-builder > Design your page with live preview, themes, layouts, and your services with prices. --- title: The page builder description: Design your page with live preview, themes, layouts, and your services with prices. --- The builder at `/a/page/design` is a full-screen editor: settings on the left, a live preview of your real page on the right. Everything you change updates the preview instantly; nothing goes live until you hit Save (and Publish, the first time). There are four tabs: Profile, Services, Sections, and Design. ![The page builder with the Profile tab open and the live preview on the right](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-builder-profile.png?v=3) ## Profile Who you are and where your audience lives. - **Photo**: upload a square photo (PNG, JPG, or WebP, up to 5MB), or keep your account photo. - **Name** and **headline**: the one-liner under your name. Keep the headline concrete ("Weekly videos for indie SaaS founders" beats "Content creator"). - **Bio**: a rich-text editor with bold, lists, and links, up to 2,000 characters. - **What you promote** (required): pick up to three kinds of brands you want to work with, from the same list brands choose at signup (SaaS, e-commerce, courses, fintech, health and fitness, and so on). This is how brands and our reviewers know who your page is for, and a page cannot be published without it. - **Topics** (optional): type a topic and press Enter to add it as a chip, up to six. Brands filter by these too, so use the words your niche actually searches. - **Links**: your website plus your social accounts. Socials are verified, not typed: hit **Verify** on a platform row and the provider's consent screen opens in a new tab (nothing you are editing is lost); once connected, the account shows on your page with a checkmark and live follower counts. Platforms that cannot be verified yet simply do not appear. - **Recent work**: a short free-text section for recent wins. Reviewers and brands read it. - **Taking bookings from** and **availability note**: an optional date and a short note ("Booking two sponsored videos for October"). Brands see them on your page and on proposals; leaving both empty simply means you are available now. - **Open booking dates**: publish specific dates you can deliver, each with an optional note like "Newsletter issue" (up to 60 dates, up to a year out). The next few show as a **Next openings** strip on your page, and a brand filling in your work with me form can pick one as their preferred timing, which then shows on the request in [Brand deals](/docs/affiliates/bookings). ## Services What brands can book, with prices. Add up to 12 services and drag the grip handle to reorder them; each one has: - A **type** picked from a list, with **Custom offer** first for anything that does not fit, then dedicated video, integration, newsletter feature, podcast ad, post, bundle, and more. - A **name** and a **price**. Price is a structured amount with its own currency picker (135+ currencies supported), formatted on your public page; older free-text prices keep rendering until you re-enter them. - **What's included**: a rich-text description with bold, lists, and links. This is what opens when a visitor clicks the service, so spell out deliverables, timing, and revisions. - An optional **photo** for the card. - Optional **spots**: how many you take per week, per month, or per issue, plus a **Booked out** switch that pauses booking while keeping the card on the page. Both show on the service card, so scarcity is visible instead of implied. The **bundle** type turns a service into a package: pick 2 to 6 of your other services and set your own price for the combo. You can also add a savings badge, from 1% to 90%, for what the bundle saves versus booking each service separately; on your page it shows as a "Save X%" pill next to the list of exactly what is included. Visitors can select services directly in your work with me form, and Rekomi totals them up for the brand. ## Sections Your page, in the order you want. The Sections tab lists every section; drag them into any order and show or hide each one with the eye (your name, your stats, and the booking form stay pinned). ![The Sections tab with draggable section rows and the add buttons for custom sections](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-builder-sections.png?v=1) Beyond the built-in About and Services sections, you can add up to 12 of your own: - **Text section**: a title and a short text, up to 4,000 characters. Good for "How I work", testimonials, or anything your page is missing. - **Links**: a titled list of up to 12 links, each with its own label, so your link-in-bio can live on the same page as your services. - **Past partners**: a wall of brands you have worked with, up to 24. Each one is a brand name with an optional logo, uploaded like any page photo; entries without a logo show as simple text chips. Hidden sections stay in the builder but never render publicly. ## Design ![The Design tab with layouts, the 12 themes, background style, and colors](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-builder-design.png?v=1) - **Layout**: four arrangements: banner (photo band on top), centered, two-column, and showcase (full-height side photo). - **Theme**: 12 palettes from clean white (Mono) to dark (Midnight, Aurora, Luxe). Themes with a gradient background also offer a solid-color switch. - **Banner color**: on banner and showcase layouts, the color used when you have no background photo. - **Accent color**: any hex color for buttons, links, and price tags. Rekomi keeps the text on top readable automatically. - **Background photo**: a wide photo (PNG, JPG, or WebP, up to 5MB). Where it appears depends on the layout: the top band on banner, the side panel on showcase, or behind the whole page on centered and two-column. ## Preview and publish "Open full preview" shows the page exactly as a visitor will see it, marked "Draft preview. Only you can see this." The form is visible but disabled until you publish. Save keeps your draft; Publish puts it live at `rekomi.com/@you` immediately and takes you back to your Creator page. Publish needs two things: your name under About you, and at least one verified social account (hit **Verify** on a platform row under Links). The bar above the editor names whichever one is still missing. You can keep editing after publishing; saves to a live page apply right away. --- # Your partners Source: https://rekomi.com/docs/affiliates/partners > Every brand relationship in one list, with deals, requests, and last activity. --- title: Your partners description: Every brand relationship in one list, with deals, requests, and last activity. --- `/a/partners` is your partner list: one row per brand you have worked with, plus a row for each contact who has reached out through your page, built automatically from your deals and work-with-me requests, newest activity first. It answers "who am I talking to, and when did we last move" without keeping a spreadsheet on the side. ![The Partners list with brand rows, deal and request counts, and Message buttons](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-partners.png?v=1) Each row shows the brand and when something last happened. Rows that come from deals also show how many deals you have together and the latest one with its status; rows that come from page requests show your contact there and how many requests they have sent. The search box filters by brand, contact, or deal title. The list is read-only on purpose: the actions live where the work happens. **Message** jumps straight into the right [conversation](/docs/affiliates/messages), and the deals themselves live over in [Brand deals](/docs/affiliates/bookings). Two honest notes. Contact details on request rows are whatever the sender typed on your public page, so treat them as unverified until you have talked. And a brand shows up here the moment they book you or send a request; there is nothing to add or maintain by hand. --- # Getting paid Source: https://rekomi.com/docs/affiliates/payouts > When the money lands, methods, and what to do if a payout fails. --- title: Getting paid description: When the money lands, methods, and what to do if a payout fails. --- `/a/payouts` is your payout history. Every batch a brand has run that included you shows up here, with the status, the method, and the amount. ## What you see ![The affiliate payouts page listing payout batches with date, period, method, status, and amount](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-payouts.png?v=5) Commission payouts and [deal releases](/docs/affiliates/deals-and-payments) both land here; a deal release is a normal payout row with the deal's name in it. Columns in the payout table: - **Date**: when the brand initiated the payout. - **Period**: the date range of conversions included (e.g., "Apr 1 - Apr 30"). - **Method**: Stripe Express, bank deposit, or PayPal, determined by your payout country (see [Payout countries](/docs/affiliates/supported-countries)). - **Status**: In transit (created or on its way), Paid, or Failed. - **Amount**: what lands in your account for this batch, shown net of the withdrawal fee (see below), in the currency the brand pays in. Each row has a Breakdown link under the amount. It expands the payout's line items: what you earned on each commission in its native currency, the mid-market rate it converted at and the date of that rate (lines already in USD say "no conversion"), and any amount reversed after a refund. Payouts created before line items were recorded have no Breakdown link. A failed payout also shows its failure reason inline under the row. **Owed back from refunds.** If a customer is refunded after you were already paid the commission on their order, the refunded share is owed back. The page shows the amount in a short notice, and it comes off your next payout automatically; there is nothing to do on your side. A brand can also reverse the paid commission on Stripe right away, in which case nothing more is deducted. ![A failed payout with its reason shown inline under the row](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-payout-failed.png?v=1) The table shows 25 rows per page and covers your 200 most recent payouts. ## Withdrawal fee You keep 100% of your commission. The one deduction Rekomi makes is a small standard withdrawal fee. For Stripe payouts, that fee is a flat **$2.25 plus 0.25%** of the payout. It covers the conventional cost of moving money to you (Stripe's per-account and per-payout charges) and handling the tax paperwork, and Rekomi takes no margin on it. So a $500 payout arrives as $500 minus ($2.25 + $1.25) = $496.50. For bank-deposit payouts, the withdrawal fee is the exact cost of the international transfer: $1.50 plus a small percentage that depends on your country (for example about 2% to the Philippines, 1.75% to India or Türkiye), passed through with no Rekomi margin. So a $500 payout to the Philippines arrives as roughly $488.70 in your local currency, and your bank deducts nothing further. For PayPal payouts, the withdrawal fee is 2% of the amount sent, capped at $20, charged by Rekomi (exactly PayPal's own sending cost). PayPal deducts nothing on arrival: the amount you see is the amount that lands in your PayPal balance. The flat $2.25 is charged **just once per calendar month**, mirroring Stripe's once-a-month account charge. If you're paid more than once in the same month, every payout after the first carries only the 0.25% and no $2.25, so getting paid more often never costs you extra. The fee is the affiliate's standard pass-through cost; it is separate from the platform fee the brand pays Rekomi (3% of the payout), which never comes out of your earnings beyond this withdrawal fee. Your dashboard shows pending and paid balances; the amount that reaches your account is always net of the withdrawal fee. ## How payouts work Brands run payouts from their dashboard, either on demand or on an automatic daily, weekly, or monthly schedule they configure. For an on-demand batch, the brand picks which conversions to include (typically every payable balance for affiliates they want to pay) and runs it; automatic runs pay every eligible balance on the schedule. Either way, payouts settle to your connected Stripe Express account, straight to your bank as a local-currency deposit, or to your PayPal account, depending on your payout method. Some brands are new to Rekomi and not yet verified; their runs dispatch 7 days after their funding lands, and a small number of runs are checked by Rekomi before they go out. Your payout page shows the expected date either way, and you are never asked to do anything about it. Each batch creates one payout record per affiliate, settled in USD (commissions earned in other currencies convert at the most recent published mid-market reference rate available when the payout runs, and the native amount, the applied rate, and the rate's date are recorded on the payout). Yours appears in `/a/payouts` immediately with the status In transit. ## Payout reviews Rekomi checks every affiliate account automatically before money moves, and a small number of payouts are looked at by a person at Rekomi before they are sent. The check only ever affects money movement: your links, tracking, and commissions keep working, and nothing is forfeited while a payout waits. A held commission stays yours and is paid once the review ends. When a payout of yours is affected, your payout settings (`/a/settings`, under Payout method) say so in plain words. You will see exactly one of these: - "Your first payout will be sent on or after \{date\}. There is nothing you need to do." A short waiting period on a first payout after you connect a payout method. - "Your payout is being reviewed by Rekomi before it is sent and will be released by \{date\} unless we contact you. There is nothing you need to do." A review with a release date already set. - "Your payout is being reviewed by Rekomi before it is sent. We will email you if we need anything from you." A person at Rekomi is looking. - "Please confirm who you are with a quick ID check so we can send your payouts." This comes with a Confirm my identity button. The check runs on Stripe Identity with a photo of your government ID and a selfie; Stripe holds the images, and Rekomi keeps only the outcome and the name on the document. - "Rekomi needs to review your account before a new payout method can be added. We have been notified and will look shortly." Shown when adding a new payout method is paused pending a review. Everything you already set up keeps working. Under every one of these there is an Ask Rekomi to look button. It opens a short form where you can add a note, and a person at Rekomi then reviews your account and emails you. You can also write to support@rekomi.com. A reviewer can change every outcome of the automatic check; nothing in it closes an account or reverses a commission on its own. Brands see none of the detail. On their side, a payout that is waiting on Rekomi simply reads "Payout under review by Rekomi", and the rest of their run goes out as normal. ## Payout terms Near the top of `/a/payouts`, each brand's payout terms are stated in one plain line: how often they pay (on demand, daily, weekly, or monthly and on which day), how long after a sale a commission becomes available (the brand's refund-hold window), and any minimum payout. When one brand runs campaigns with different terms, each campaign gets its own labeled line. The same terms also appear on each campaign's page, and they are the same terms the brand sees stated on their own payouts page, so both sides read the same rules. ## Status flow - **In transit**: the payout has been created or the transfer is on its way, waiting for settlement on the payment provider's side. - **Paid**: settled. The money is in your account. - **Failed**: error. Check the failure reason shown under the row. For Stripe Express: settlement is typically 1-3 business days for U.S. domestic, 3-7 days for international. For bank deposits: the money arrives in your local currency, typically within 1-7 business days depending on your country. ## What if a payout fails Common causes: - Your Stripe Connect account got suspended (account-level issue). - A dispute or KYC hold on your side. - A bank-level reject (closed account, fraud flag). When a payout fails, the brand is alerted by email, and the failure reason appears on your `/a/payouts` page under the failed row. To resolve: 1. **Read the failure reason** shown under the failed row. It usually tells you what to fix. 2. **Fix the underlying issue**. Reactivate your Stripe Connect account, contact your bank, etc. 3. **Ask the brand to retry**. They click Retry in their Failed tab. The payout goes back to In transit. ## One payout method You have one payout method per account, determined by your country and locked once you connect. That method is Stripe Express in the 41 countries Stripe supports (including the US), a direct bank deposit in 60 more (you add your bank details once on a secure Stripe-hosted form), and PayPal in 31 more. The method is selected automatically from your country; you do not choose it by hand. It applies to every campaign you join, so there is nothing to pick per brand and no default to set. If your country's payout method ever changes, contact support. See [Payout countries](/docs/affiliates/supported-countries) for which method covers your country. ## Across multiple brands If you are an affiliate for multiple brands, each brand runs their own payout batches independently. You see one row per batch in `/a/payouts`; the rows do not name the brand today, so match them by date and amount. Every batch pays to your single account-level payout method. ## Currency Your earnings are recorded in the currency the brand's sales come in (usually USD), and you can see those native amounts on every commission. Payouts settle in USD: when a batch runs, any non-USD earnings convert at the most recent published mid-market reference rate available when the payout runs (never more than seven days old), with no Rekomi markup. The payout records the native amount, the rate, and the date that rate was published for every commission, and each row's Breakdown shows them. That published date is often the previous business day rather than the payout date itself, and the recorded date is the one that governs. What arrives on your side then depends on your payout method: - **Stripe Express**: the payout arrives in USD. If your bank account is in another currency, Stripe converts it at Stripe's own exchange rate, which includes Stripe's currency-conversion charge; Rekomi adds nothing to it. Where your bank can receive USD, adding a USD bank account in your Express dashboard avoids the conversion. - **Bank deposit**: Stripe converts at send, so the deposit arrives already in your local currency (LKR in Sri Lanka, PHP in the Philippines, INR in India, and so on). The conversion is part of the at-cost withdrawal fee above; there is no Rekomi margin on the rate, and your bank has nothing left to convert. - **PayPal**: funds arrive in USD in your PayPal balance with no receiving fee. If you convert them to another currency, or your country's PayPal account holds only the local currency and converts automatically, PayPal applies its own exchange rate, which includes PayPal's conversion charge; Rekomi adds nothing to it. ## Withdrawal For Stripe Express: by default, Stripe handles auto-payout to your bank on a 2-day rolling schedule. You can change this to manual payout from your Stripe Express dashboard. For bank deposits: there is nothing to withdraw; the money lands directly in your bank account in your local currency. For PayPal: funds land in your PayPal balance, and you withdraw to your bank through whatever options PayPal offers in your country. ## Tax reporting A tax form is required before your **first** payout on every rail: a W-9 if you are a U.S. person, a W-8BEN or W-8BEN-E otherwise. Upload it once from your payout settings; until it is on file, brands see "Tax form missing" beside your name and your balance waits. Rekomi never deducts withholding from a payout. For U.S. residents earning at or above the IRS reporting threshold in a calendar year ($2,000 for 2026 payments): Rekomi (Uplup Inc.) is the payer of record (the entity that reports and files your tax form) and issues your 1099-NEC, delivered through Stripe in January. See [Tax forms](/docs/affiliates/tax-forms). For non-U.S. residents: your W-8BEN or W-8BEN-E is the relevant document and stays valid through the end of the third year after you sign it. ## Export There is no CSV export button on `/a/payouts` today. For end-of-year totals, your 1099-NEC (if you are owed one) reports payments made to you during the year, not commissions earned or accrued, and you can also ask the brand to pull payout data via their API. --- # Promote Rekomi and earn Source: https://rekomi.com/docs/affiliates/promote-rekomi > Join the Rekomi affiliate program and earn recurring commission for every brand you refer. --- title: Promote Rekomi and earn description: Join the Rekomi affiliate program and earn recurring commission for every brand you refer. --- Rekomi runs its own affiliate program (a referral program, if you prefer that word). If you recommend Rekomi to brands, creators, or agencies, you earn a recurring share of every subscription you bring in. The short version lives on the [program page](https://rekomi.com/affiliates); this doc is the full detail. ## The terms - **25% commission**, recurring: you earn on the first payment and on renewals for as long as the customer stays subscribed. - **60-day cookie window.** Anyone who clicks your link has 60 days to start a Rekomi plan and still count as your referral. - **Last-touch attribution.** If a customer clicks several affiliates' links, the *most recent* one within the window gets the credit. - **$50 minimum payout.** Commissions are paid out once your approved balance reaches $50. - **Applications are reviewed.** New affiliates land in Pending until we approve them, which is usually quick. **Refunds are handled automatically.** If a customer you referred is refunded, that conversion is reversed so commissions always reflect real, kept revenue. ## Join Apply at [affiliates.rekomi.com/join](https://affiliates.rekomi.com/join). You create your account right when you apply, so you land in your affiliate dashboard at [app.rekomi.com](https://app.rekomi.com) straight away. Your referral link, live click and conversion stats, and your earnings all appear there; the link unlocks as soon as you are approved. ## Your referral link Your link looks like `https://rekomi.com/?via=your-name`. Share that anywhere; your referral is stored first-party on the `rekomi.com` domain, so the attribution survives the whole signup journey (marketing site to app to checkout) and isn't dropped the way third-party trackers are. You can drop `?via=your-name` onto any Rekomi marketing URL (for example `https://rekomi.com/pricing?via=your-name`) and it tracks the same way. ## Referring Shopify, Wix and BigCommerce stores Stores that install Rekomi from the Shopify App Store, the Wix App Market or the BigCommerce App Marketplace count as your referrals too, with one rule that matters: **the store has to reach the marketplace through rekomi.com with your link**. There is no way to carry a referral through the marketplaces' own listing pages, so a merchant who hears "get Rekomi" and searches the App Store directly can never be credited to anyone. Here's how to do it right: 1. Share the platform-specific link: `https://rekomi.com/integrations/shopify?via=your-name` for Shopify stores, `https://rekomi.com/integrations/wix?via=your-name` for Wix sites, or `https://rekomi.com/integrations/bigcommerce?via=your-name` for BigCommerce stores. (Any rekomi.com page with your `?via=` works; these three are built to sell the apps.) 2. Your referral clicks the **Get it on the Shopify App Store** / **Wix App Market** / **BigCommerce App Marketplace** button on that page and installs. That click-through is what connects the install to you, and it's matched when the install happens soon after (within about two days of the click-through; the underlying link click itself has the normal 60-day window). 3. They pick a plan inside the app (usually with a 14-day free trial, billed through their platform; Wix trials are once per Wix account, so some stores pay from day one). Once real billing starts, your commission is created, and it recurs monthly like any other referral. Commissions from marketplace installs start as Pending and are reviewed like all program commissions. If your referral cancels, unapproved commissions for that store are reversed automatically. BigCommerce stores work the same way: the Rekomi app is live in the BigCommerce App Marketplace, and a store that installs it from your link is your referral. The one habit to build: always send stores to your link, never to a marketplace search. "Install it from rekomi.com/integrations/shopify" (or /wix, or /bigcommerce) is the phrasing that gets you paid. ## Who to promote Rekomi to Rekomi is affiliate-marketing software for subscription businesses. The best fits: - **SaaS and AI-tool founders** who want a private affiliate program with native Stripe, Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie tracking (any other gateway via the server-to-server API). - **Course and membership creators** monetizing recurring revenue. - **Shopify, Wix, BigCommerce, and other ecommerce stores** that want a real affiliate program with payouts and tax paperwork handled (Shopify, Wix and BigCommerce stores install straight from their app stores; see the section above for how to route them). - **Agencies and consultants** who set up growth programs for clients; Rekomi is an easy recommendation, and recurring commission compounds across every client you onboard. ## Ideas that convert - A hands-on **review or tutorial** ("how I set up an affiliate program in 20 minutes"). - A **comparison** against tools you've used (Rewardful, FirstPromoter, Tapfiliate, PartnerStack). - A **newsletter mention** to a founder/creator audience. - A line in your **"tools I use"** page or video description. Concrete, specific, and honest beats generic, the same advice as your [creator profile](/docs/affiliates/creator-profile). ## Getting paid Payouts work exactly like any other Rekomi campaign: connect your Stripe account once and approved commissions are paid out to you. See [Stripe Connect](/docs/affiliates/stripe-connect) and [Payouts](/docs/affiliates/payouts) for the details. --- # Quickstart for affiliates Source: https://rekomi.com/docs/affiliates/quickstart > Sign up, set up your creator profile, and apply to your first campaign. --- title: Quickstart for affiliates description: Sign up, set up your creator profile, and apply to your first campaign. --- This guide walks you from "I have an audience" to "I have a tracking link and I am earning commission". Total time: about fifteen minutes. ## 1. Get into Rekomi There are two ways to land in your creator dashboard for the first time: - **You applied to a brand's campaign first.** The moment you apply, Rekomi emails you a 6-digit code; enter it and you land in your dashboard, with your account created on the spot if you are new (no password or sign-up form) or signed in if your email already has one. Your application attaches to your account automatically by email, and the campaign appears in `/a` as under review. Your referral link unlocks the moment the brand approves you. This is the most common path. If you close the tab before finishing, the approval email also carries a one-click sign-in as a backup. - **You're starting from scratch (no application yet).** Go to [rekomi.com/sign-up](https://rekomi.com/sign-up) and pick "I'm a creator". You sign up with email and password or a Google account. When your account is created, Rekomi sets up your creator profile automatically; you fill in the details next. Either way, no payment information yet; you set that up later when you have earnings to collect. ## 2. Build your creator profile You land on `/a` (your affiliate home). ![Affiliate home dashboard with KPI cards and the campaigns list, shown here for an established account](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-home.png?v=4) The screenshot above shows the home page once you are up and running. On your first visit, the dashboard prompts you to finish your creator profile: a short one-page setup asks for your bio, niche, audience details, and the country where you want to receive payouts. Your bio, niche, and payout country are required to complete setup; everything else is optional, and you can edit it all later from Settings. Fields worth filling: - **Bio** (required): two or three sentences about what you do. - **Niche** (required): the topic area you cover (SaaS, AI tools, productivity, design, etc.). - **Payout country** (required): the country where you want to receive payouts. - **Verified socials**: connect your accounts from your Creator page so brands see live, checked follower counts instead of a typed estimate. - **Portfolio URL**: link to your best content. Reviewers look at this when evaluating applications. - **Recent results**: short bullet of recent wins (a popular video, a viral tweet, a paid placement). A complete profile dramatically improves your approval rate when you apply to campaigns. ## 3. Browse and apply The creator network is launching soon. When it opens, `/a/network` will show curated campaigns you can browse by brand category and search; each card shows the brand logo and name, the campaign, its real commission terms (rate, recurring or not, cookie window), an instant-approval badge when it applies, and a trust line showing whether the brand has a verified payout method and a history of sent payouts. Every campaign in the network belongs to a brand with an active paid plan and payout funding in place. Until then, brand invite links and direct application pages work today. When you find a campaign worth promoting: 1. Read the campaign description and commission summary. 2. Write a short pitch: how you would promote it, what audience you would target, what content you would create. 3. Click apply. Your application goes into the campaign's review queue. Status flows: `Submitted -> UnderReview -> Approved | Rejected`. You can also apply directly via a brand's public application page if they share a `{brand}.rekomi.com/{campaign}` URL with you. See [Direct applications](/docs/affiliates/direct-applications). ## 4. Once approved The campaign flips from under review to active in `/a` on its own, and your referral link and stats appear there automatically. There is nothing extra to click. We also email you when you are approved, and that email has a one-click sign-in if you are not already signed in (single-use, expires in 14 days). If you closed the tab before creating your account, use [Create your account](/sign-up/affiliate) with the same email you applied with and your application links automatically. Your referral link name (the industry calls this a slug) is your unique identifier: **your referral link, shared across every campaign you join**. You add it to any link to the brand, usually as `?via=your-name`. On sale-model campaigns you can also create extra product or page links from the campaign page; see [Tracking link](/docs/affiliates/tracking-link). ``` https://thebrand.com/?via=your-name ``` Some brands use a custom parameter name instead of `via`; each campaign page shows the exact link format to use for that brand. You can see and change it in [Settings](/docs/affiliates/tracking-link). Share these links anywhere: your bio, your video descriptions, your blog posts, your newsletter. Every click that turns into a customer earns you commission. ## 5. Connect Stripe for payouts When you start earning, you need somewhere to receive the money. Go to `/a/settings` and pick the country where you are based. ![Affiliate settings page showing the referral link section, the connected payout method, and the tax form section](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-settings.png?v=4) Rekomi shows the right payout method for your country: Stripe Express where Stripe is supported (the U.S. and 40 other countries), direct bank deposits in 60 more, and PayPal in 31 more, 130+ combined. For Stripe, click "Connect with Stripe" and complete Express onboarding (about 10 minutes for U.S. residents, longer for international). For bank deposits, add your bank details on a secure Stripe form (a couple of minutes). For PayPal, add your PayPal email in Settings. Your payout method applies to every campaign you join. ## 6. File your tax form If you are based in the U.S. and are paid at or above the IRS reporting threshold in a calendar year ($2,000 for 2026 payments, counted on payments made rather than commissions earned), the IRS requires you to provide your tax info (Form W-9). If you are outside the U.S., you provide W-8BEN (individuals) or W-8BEN-E (businesses). If you're paid through Stripe, Stripe collects your tax details (W-9 for U.S. creators, W-8 otherwise) during payout onboarding and holds the form, so there's nothing to upload in Rekomi. Creators paid via PayPal or bank deposit upload a W-8 in Settings; those payouts are held until it is on file. ## What's next - [Tracking link](/docs/affiliates/tracking-link): how the link actually works. - [Earnings](/docs/affiliates/earnings): conversion list, statuses, refund handling. - [Payouts](/docs/affiliates/payouts): when and how the money lands. --- # Connect Stripe for payouts Source: https://rekomi.com/docs/affiliates/stripe-connect > Stripe Express onboarding, one payout account for every campaign, what data Stripe collects. --- title: Connect Stripe for payouts description: Stripe Express onboarding, one payout account for every campaign, what data Stripe collects. --- To receive payouts via Stripe, you connect a Stripe Express account. Express is Stripe's lightweight onboarding flow built for marketplaces. It takes about ten minutes for U.S. residents and longer for international. Stripe is the payout method for affiliates based in the 41 countries Stripe supports (including the U.S., most of Europe, Canada, Australia, Japan, and Mexico); your payout method is determined by the country you choose, not picked by hand. See [Payout countries](/docs/affiliates/supported-countries) for the full list. ## Why Stripe Three reasons: 1. **Speed**. Stripe settles transfers in 1-3 business days. 2. **Compliance**. Stripe handles your identity verification (KYC) and anti-money-laundering checks. You do not have to. For US tax reporting, Rekomi (Uplup Inc.) is the payer of record (the entity that reports and files your tax form) and issues any 1099-NEC you are owed through Stripe at year end; Stripe Express collects your W-9 or W-8 during onboarding. See [Tax forms](/docs/affiliates/tax-forms). 3. **Transparent fees**. You keep 100% of your commission. The only deduction is a small standard withdrawal fee (a flat $2.25 plus 0.25%, charged just once per monthly payout, at no margin to Rekomi; see [Payouts](/docs/affiliates/payouts#withdrawal-fee)), and the brand pays Rekomi a platform fee on the payout (currently 3%) that does not come out of your earnings beyond that withdrawal fee. If Stripe does not operate in your country, you are paid via PayPal instead. Rekomi routes you to one payout method or the other based on your country; there is nothing to choose by hand. ## How to connect `/a/settings` has the Payout method section. Pick your country first: Stripe locks the account country at setup, so it cannot be changed later. Once you choose a Stripe-supported country, the Stripe Express card appears with a "Connect with Stripe" button and a "Not connected" status. ![The affiliate settings page with the payout method section showing the Stripe Express card and its status pill (shown here connected), below the referral link section and above the tax form section](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-settings.png?v=4) Click Connect. You land on Stripe's hosted onboarding page. Stripe collects: - Your legal name and date of birth - Your address - Your government-issued ID or SSN (last 4 in the U.S.) - Your bank account routing details - (For business accounts) entity name, EIN, beneficial owners This information goes to Stripe, never to Rekomi. Stripe is the regulated entity. After you complete the form, Stripe returns you to your Payouts page at `/a/payouts`, and your settings show the "Connected" status. ## One account for every campaign You connect once. That single Stripe Express account is your account-level payout identity: it applies automatically to every campaign you join, current and future. You do not connect a separate account per brand, and you never see a list of per-campaign connect buttons. Stripe links your KYC to the one account, and Rekomi routes each brand's payout to it. ## What if Stripe is not in your country Your payout method follows your country: - **Stripe countries** (42, including the U.S.): you connect Stripe Express, as above. - **Bank deposit countries** (60 more): you are paid straight to your local bank account. Add your bank details on `/a/settings` (plus your W-8 tax form if you are outside the US), and you are included in payout runs like any other affiliate. - **PayPal countries** (31 more): you are paid to your PayPal account. Add your PayPal email on `/a/settings` (plus your W-8 tax form if you are outside the US), and you are included in payout runs like any other affiliate. - **Anywhere else**: payouts are not available in your country yet. You can still join campaigns and earn; we are adding coverage over time. You do not pick the payout method and brands do not enable it per affiliate; Rekomi routes each payout to the method your country uses. ## Onboarding status Your status on `/a/settings` can be: - **Not connected**: you have not started. - **Finish setup**: Stripe still needs details before you can be paid. Click "Finish Stripe setup" to resume Stripe's flow; this is also where you land if Stripe flags a verification issue or a missing document. - **Connected**: ready to receive payouts. ## Updating your Stripe details To change bank account, business entity, address, or any other field, click "Manage on Stripe" from `/a/settings`. This reopens Stripe's secure hosted flow, where you update your details. Changes sync back to Rekomi automatically. ## What Rekomi sees Rekomi sees: - Your Stripe Connect account ID - Whether the account is in good standing - Whether you can accept transfers Rekomi does not see: - Your bank account number - Your full SSN - Your tax forms (those go directly to Stripe) - Your personal address (Stripe-collected; Rekomi sees only what Stripe surfaces to platforms) If you are paid via Stripe Express, Stripe collects your tax form during onboarding and there is nothing to upload to Rekomi. See [Tax forms](/docs/affiliates/tax-forms) for details. ## When payouts arrive After a brand runs a payout batch, your payout shows in `/a/payouts` as In transit, then Paid. The Stripe-side settlement is typically 1-3 business days for U.S. domestic transfers and 3-7 days for international. Failed transfers (closed account, KYC hold, dispute) show as Failed with a reason. Resolve on Stripe's side, then ask the brand to retry from their Failed tab. --- # Payout countries Source: https://rekomi.com/docs/affiliates/supported-countries > Which payout method covers your country, and the withdrawal fee. Stripe Express (41 countries), bank deposits via Stripe (60 more), or PayPal (31 more), 130+ countries combined. --- title: Payout countries description: Which payout method covers your country, and the withdrawal fee. Stripe Express (41 countries), bank deposits via Stripe (60 more), or PayPal (31 more), 130+ countries combined. --- This page is about **affiliates receiving** payouts. If you are a brand asking where your business can fund payouts from, that is a different (and much longer) list: see [Funding countries](/docs/brands/funding-countries). Your payout method is determined by your country, not chosen by hand. When you pick your payout country at signup, Rekomi routes you to the right payout method automatically: 1. **Stripe Express** if your country is one of the 42 Stripe-supported countries below (this includes the United States). 2. **Bank deposit** if your country is one of the 60 bank-deposit countries below: add your bank details once on a secure Stripe-hosted form (no Stripe account needed) and payouts arrive as direct deposits in your local currency, usually within a few business days. 3. **PayPal** if your country is covered by neither of the above but is PayPal-supported (the 30 countries below): add your PayPal email in Settings and you are paid through the same payout runs as everyone else. 4. **Manual** if your country is covered by none of these: you can still join campaigns and earn, but automated payouts are not available yet and a brand would pay you out of band. Combined, the three methods cover 130+ countries. The United States always routes to Stripe Express. Your payout method also determines where your tax form lives; see [Tax forms](/docs/affiliates/tax-forms). ## Every country, searchable Every country, its payout method, and the withdrawal fee (the only cost you ever pay, a pass-through of the processor's own charge with no Rekomi margin). Search by name or filter by method: The withdrawal fees in full: on Stripe Express, $2.25 charged at most once per calendar month (no matter how many payouts you receive that month) plus 0.25% of each payout. On bank deposits, the exact cost of the international transfer: $1.50 plus a small percentage that depends on your country (shown per country in the table above), passed through with no Rekomi margin. On PayPal, 2% of the amount sent, capped at $20, and PayPal deducts nothing when it arrives; a currency conversion on your Stripe or PayPal side, if any, is at that provider's own rate. Worked examples are in [Payouts](/docs/affiliates/payouts#withdrawal-fee). The static lists below are the same data, for reference. ## Stripe Express countries (41) Affiliates in these countries connect a Stripe Express account and are paid through Stripe. | | | | | |---|---|---|---| | 🇦🇺 Australia | 🇦🇹 Austria | 🇧🇪 Belgium | 🇧🇬 Bulgaria | | 🇨🇦 Canada | 🇭🇷 Croatia | 🇨🇾 Cyprus | 🇨🇿 Czech Republic | | 🇩🇰 Denmark | 🇪🇪 Estonia | 🇫🇮 Finland | 🇫🇷 France | | 🇩🇪 Germany | 🇬🇮 Gibraltar | 🇬🇷 Greece | 🇭🇰 Hong Kong SAR China | | 🇭🇺 Hungary | 🇮🇪 Ireland | 🇮🇹 Italy | 🇱🇻 Latvia | | 🇱🇮 Liechtenstein | 🇱🇹 Lithuania | 🇱🇺 Luxembourg | 🇲🇹 Malta | | 🇲🇽 Mexico | 🇳🇱 Netherlands | 🇳🇿 New Zealand | 🇳🇴 Norway | | 🇵🇱 Poland | 🇵🇹 Portugal | 🇷🇴 Romania | 🇸🇬 Singapore | | 🇸🇰 Slovakia | 🇸🇮 Slovenia | 🇪🇸 Spain | 🇸🇪 Sweden | | 🇨🇭 Switzerland | 🇹🇭 Thailand | 🇦🇪 United Arab Emirates | 🇬🇧 United Kingdom | | 🇺🇸 United States | | | | ## Bank deposit countries (60) Affiliates in these countries add their bank details on a Stripe-hosted form and are paid by direct deposit in their local currency. Outside the US, a W-8 upload in Settings is required before the first payout, same as PayPal. | | | | | |---|---|---|---| | 🇦🇱 Albania | 🇩🇿 Algeria | 🇦🇬 Antigua and Barbuda | 🇦🇲 Armenia | | 🇧🇸 Bahamas | 🇧🇭 Bahrain | 🇧🇯 Benin | 🇧🇹 Bhutan | | 🇧🇦 Bosnia and Herzegovina | 🇧🇼 Botswana | 🇧🇳 Brunei | 🇨🇳 China | | 🇨🇷 Costa Rica | 🇨🇮 Côte d'Ivoire | 🇩🇴 Dominican Republic | 🇪🇨 Ecuador | | 🇪🇬 Egypt | 🇸🇻 El Salvador | 🇪🇹 Ethiopia | 🇬🇲 Gambia | | 🇬🇹 Guatemala | 🇬🇾 Guyana | 🇮🇸 Iceland | 🇮🇳 India | | 🇮🇩 Indonesia | 🇮🇱 Israel | 🇯🇲 Jamaica | 🇯🇴 Jordan | | 🇰🇪 Kenya | 🇰🇼 Kuwait | 🇲🇬 Madagascar | 🇲🇾 Malaysia | | 🇲🇺 Mauritius | 🇲🇩 Moldova | 🇲🇨 Monaco | 🇲🇳 Mongolia | | 🇲🇦 Morocco | 🇲🇿 Mozambique | 🇳🇦 Namibia | 🇲🇰 North Macedonia | | 🇴🇲 Oman | 🇵🇰 Pakistan | 🇵🇦 Panama | 🇵🇪 Peru | | 🇵🇭 Philippines | 🇶🇦 Qatar | 🇷🇼 Rwanda | 🇱🇨 Saint Lucia | | 🇸🇲 San Marino | 🇸🇳 Senegal | 🇷🇸 Serbia | 🇿🇦 South Africa | | 🇱🇰 Sri Lanka | 🇹🇼 Taiwan | 🇹🇿 Tanzania | 🇹🇹 Trinidad and Tobago | | 🇹🇳 Tunisia | 🇹🇷 Turkey | 🇺🇿 Uzbekistan | 🇻🇳 Vietnam | ## PayPal countries (31) Affiliates in these countries are paid to their PayPal account; countries covered by bank deposits above use those instead. Add your PayPal email in `/a/settings`; if you are outside the US, also upload your W-8 there, since PayPal payouts are held until it is on file. PayPal payouts carry a withdrawal fee of 2% capped at $20, charged by Rekomi; PayPal deducts nothing from the amount that arrives; converting it to another currency is at PayPal's own rate (see [Payouts](/docs/affiliates/payouts)). | | | | | |---|---|---|---| | 🇦🇩 Andorra | 🇦🇮 Anguilla | 🇦🇷 Argentina | 🇦🇼 Aruba | | 🇧🇧 Barbados | 🇧🇲 Bermuda | 🇧🇴 Bolivia | 🇧🇷 Brazil | | 🇻🇬 British Virgin Islands | 🇰🇾 Cayman Islands | 🇨🇱 Chile | 🇨🇴 Colombia | | 🇫🇰 Falkland Islands | 🇫🇴 Faroe Islands | 🇬🇪 Georgia | 🇬🇱 Greenland | | 🇬🇩 Grenada | 🇭🇳 Honduras | 🇯🇵 Japan | 🇰🇿 Kazakhstan | | 🇱🇸 Lesotho | 🇲🇸 Montserrat | 🇳🇮 Nicaragua | 🇵🇾 Paraguay | | 🇷🇪 Réunion | 🇻🇨 Saint Vincent and the Grenadines | 🇸🇦 Saudi Arabia | 🇰🇷 South Korea | | 🇸🇷 Suriname | 🇺🇾 Uruguay | 🇻🇪 Venezuela | | In some of these markets PayPal receives funds into your PayPal balance but local bank withdrawal may be limited (for example South Korea); you still receive the money in PayPal. ## Countries without automated payouts If your country has no automated method above, automated payouts are not available yet. This includes send-only markets and sanctioned or unsupported regions. You can still sign up, join campaigns, and earn; your commissions are recorded and stay on your balance until an automated method reaches your country, and we are expanding coverage over time. If you are unsure, pick your country at signup and the payout section will tell you which method (if any) covers you. The lists on this page are taken from the providers' own published coverage (Stripe's Express onboarding and Global Payouts recipient tables, and PayPal's Payouts country reference) and were last checked on September 15, 2026. They are the same lists the app routes on, so a country appears here exactly when an automated payout can reach it, and they change when a provider adds or removes a market. --- # Your tax forms Source: https://rekomi.com/docs/affiliates/tax-forms > W-9 (U.S.), W-8BEN (non-U.S. individual), W-8BEN-E (non-U.S. business), and where each is collected depending on your payout method. --- title: Your tax forms description: W-9 (U.S.), W-8BEN (non-U.S. individual), W-8BEN-E (non-U.S. business), and where each is collected depending on your payout method. --- How your tax info is collected depends on your payout method, which is set by your country (see [Payout countries](/docs/affiliates/supported-countries)). The short version: if you are paid on **Stripe Express**, Stripe collects and holds your tax form during onboarding, and there is nothing to upload to Rekomi. If you are paid via **PayPal** or **bank deposit**, you upload your W-8 form directly to Rekomi. ## Which form, and who collects it | Your situation | Form | Collected and stored by | You upload to Rekomi? | |---|---|---|---| | U.S., paid via Stripe Express | W-9 | Stripe (during Express onboarding) | No | | Non-U.S., paid via Stripe Express | W-8BEN (individual) or W-8BEN-E (business) | Stripe (during Express onboarding) | No | | Non-U.S., paid via PayPal or bank deposit | W-8BEN or W-8BEN-E | Rekomi (you upload at `/a/settings`) | Yes | The U.S. always routes to Stripe Express, so U.S. affiliates always provide a W-9 through Stripe, not through Rekomi. ## If you are paid via Stripe Express Stripe collects your tax form as part of Express onboarding, alongside your identity and bank details. There is nothing else to upload to Rekomi. Stripe holds the form; Rekomi never sees your SSN, ITIN, or full tax document. - **U.S. residents** provide a **W-9** (your SSN or EIN). - **Non-U.S. residents** provide a **W-8BEN** (individuals) or **W-8BEN-E** (businesses), which certify foreign status. If you're paid via Stripe there is no upload option at all; Stripe already holds your form, and a brand that needs tax detail gets it through its own reporting. ## If you are paid via PayPal or bank deposit Neither PayPal nor the bank-deposit flow collects tax forms the way Stripe Express does, so foreign affiliates on those methods upload their W-8 directly to Rekomi at `/a/settings`. Upload it before your first payout is due; payouts are held until a current signed W-8 is on file. `/a/settings` has the "Tax forms" widget. Pick your form type from the list: - W-8BEN: non-U.S. individuals - W-8BEN-E: non-U.S. business entities - W-9: U.S. persons (rarely needed here, since U.S. affiliates are routed to Stripe Express and provide their W-9 there) The widget has three parts: 1. **PDF picker**: upload the signed form. You fill out the IRS form on your end (or use a tax-form generator), download as PDF, and upload here. 2. **Signature**: draw with your mouse, trackpad, or touchscreen, or type your name and pick a signature style. Either way it is saved securely alongside the form. 3. **Submit**: stores the form privately and updates your status. ### Private storage The PDF is stored under a **private** path. The persisted URL is an opaque marker, not a directly-servable URL. When you or an authorized admin requests the form, the server mints a short-lived pre-signed URL (5-minute TTL) and returns that. This means: - Your SSN or ITIN never lands in a publicly accessible URL. - The form is not in any CDN cache. - If a URL is leaked, it expires in 5 minutes. ### Expiry A W-8 is valid through December 31 of the third year after you sign it. Your settings show the date your form is on file until, and we email you a reminder as it approaches expiry. Upload a freshly signed form before it expires to keep payouts flowing. ### Retention Prior uploads are retained for 7 years per IRS requirements, then deleted automatically by a scheduled retention job (it removes the stored file first, then the record). ## The 1099-NEC Rekomi (Uplup Inc.) is the payer of record (the entity responsible for U.S. tax reporting), not the individual brands. If the payouts you receive through Rekomi cross the IRS reporting threshold in a calendar year ($2,000 for 2026 payments), your 1099-NEC is issued under Rekomi's name and delivered through Stripe in January, electronically if you consented in your Stripe Express dashboard and otherwise by mail. Stripe collects your W-9 during Express onboarding, so you rarely need to send anything extra. If you're paid via PayPal or bank deposit, a current signed W-8 is required before any payout can be sent, so upload yours before your first payout is due. ## Status Your tax-form status on `/a/settings` (for the W-8 upload) is either empty (nothing uploaded), **On file** (your signed form is on file), or **Expired** (a lapsed form that needs a fresh upload). If a brand has a question about your form, they will contact you directly. If you're paid via Stripe, your tax status is part of your Stripe Express account standing rather than a Rekomi upload status. ## Updating your form You can upload a new form at any time; the new upload supersedes the prior version, and your brands see the latest form's status (its type and date, never the file itself). Common reasons to re-upload: your tax classification changed (individual to LLC, sole prop to S-corp), your address changed, there was a typo on the first upload, or your W-8 is approaching its expiry date. If you're paid via Stripe, update your details in Stripe Express instead. ## Non-U.S. residency and withholding Filing the right W-8 certifies your foreign status, so US withholding generally does not apply to commissions for services you perform outside the US. Rekomi never deducts tax withholding from a payout. Instead, if you do not have a valid W-8 on file and you're paid via PayPal or bank deposit, the payout is held until you provide one. --- # Your tracking link Source: https://rekomi.com/docs/affiliates/tracking-link > How your referral link name works, where to use it, and how to change it. --- title: Your tracking link description: How your referral link name works, where to use it, and how to change it. --- Your account link name is **your referral link identity, shared across campaigns**. It's your identity as an affiliate (the industry calls this a slug); make it your name or brand. You add it to any link to a brand you promote, and clicks that convert get credited to you. On sale-model campaigns, you can also create extra product and page links; see [Deep links](/docs/affiliates/deep-links) below. ## The URL pattern Most brands use `?via={your-name}` appended to a link to the brand: ``` https://thebrand.com/?via=your-name ``` The same link name works for every brand you partner with; you don't get a different one per campaign. A few brands use a different parameter name (for example `?ref=`); Rekomi recognizes the common industry parameters plus whatever the brand configures, and your campaign page always shows the exact ready-to-copy link for that brand. When a visitor lands on the brand's site with your link name in the URL, Rekomi's tracking attributes the click to you. If you're already in two campaigns and join a third, you keep the same link name, nothing to set up. ## Where to find your link name ![A campaign detail page showing the copy-ready affiliate link with a copy button, here a Rekomi tracking link on a campaign that also pays per click](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-campaign-detail.png?v=6) - **Settings** (`/a/settings`) shows your link name and lets you change it (see below). - Each campaign page (`/a/campaigns/{campaign-id}`) shows the full ready-to-copy link for that brand, built with the right parameter and your link name, with a copy button. ## Product and page links On campaigns that pay per sale, you are not limited to your one referral link. From the campaign page you can create product & page links (the industry calls these deep links): direct links to a specific product or page on the brand's site, each carrying its own code so its clicks and sales are tracked separately from your referral link. You can hold up to 20 links per campaign, including your referral link. If the brand connected Shopify, you can pick the product from a picker instead of pasting a URL. Your account link name still stays your referral link identity across every campaign; product & page links are extra links for a specific product or page, not a replacement for it. See [Product & page links](/docs/affiliates/deep-links) for how to create and use them. ## Sub-IDs You do not need a separate link for every place you share. Add a tag to the end of your one link and Rekomi splits your results by that tag: ``` https://thebrand.com/?via=your-name&sub=yt https://thebrand.com/?via=your-name&sub=newsletter https://thebrand.com/?via=your-name&sub=bio ``` The tag is called a sub-ID, the same idea as a sub ID on the large affiliate networks. Rekomi records it with the click and carries it onto the sale whichever way the sale arrives: the visitor's click, a signup captured with the tag, the brand's checkout when it stamps `rekomi_sub`, or the customer's earlier tagged purchase for renewals and repeat orders. Your campaign page has a Sub-IDs section with clicks, leads, sales, commission and earnings per click for each tag, your Insights page has the same breakdown across campaigns, and the brand sees the same table on their side. A tag is lowercase letters, numbers, dots, hyphens and underscores, up to 64 characters; other characters are dropped, and `YT` and `yt` count as one tag. `subid` and `sub_id` work the same as `sub`. On a rekomi.link tracking link the tag rides along to the brand's site. A sale Rekomi cannot tie to a tagged visit shows no tag rather than a guessed one. ## Choosing and changing your link name When you first join a campaign, Rekomi sets your link name automatically: either the custom alias you picked when applying (if the brand allowed it), or a short generated one like `x7y9z2`. Some brands set their campaign to build a new affiliate's link name from their first name instead, so it reads like `?via=olivia`; if that name is already taken it gets your last initial, then a number. You can still change it afterwards in the same way. ![The Settings page with the referral link section, where you can change your link name once, above the connected payout method and tax form sections](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/affiliate-settings.png?v=4) You can **change it once** from Settings. Pick something memorable and on-brand for your audience (3-64 letters, numbers, or hyphens, with no leading or trailing hyphen), like `jane-recommends`. On a campaign's own page, you can also make a custom link for that campaign once, separately from your shared link name: look for Your referral link and "Make a custom link." It replaces your shared link name only in that campaign's links, so your other campaigns keep using your account link name. **Important:** after a link name change, links you've already shared with the old link name may stop crediting you on some campaigns, so update them wherever you've posted them. Because you can only change it once, choose carefully. (Your coupon codes are separate and are not affected by a link name change.) If you need it changed again after that, contact support. ## Where to use your link - Your social profile bios - Your video descriptions - Your blog posts and tutorials - Your newsletter - Email signatures - Your community Slack or Discord (if allowed) - Paid placements you do (sponsored tweets, sponsored newsletter ads) ## Where NOT to use your link - **Paid search ads** on the brand's name. Most brand affiliate terms ban this; it siphons their own paid traffic and gets you banned. - **Coupon code sites** unless the brand explicitly allows. Same logic. - **Audiences you do not actually own**. Buying email lists, spamming, etc., gets you removed. - **Pre-rolled links to wallpaper your audience**. If you are not adding value with your own content, you are not building the long-term relationship. Always read the brand's terms, linked from the brand's public campaign page. Each brand has slightly different rules. ## How attribution works When a visitor clicks a link carrying your link name: 1. The click registers a first-party cookie on the brand's site. Your link name is the affiliate ID stored in the cookie. 2. If that visitor signs up and pays the brand within the cookie window (default 60 days; brand-configurable), the conversion gets credited to you. 3. New commissions sit in a hold window (default 30 days, set by the brand). Once a commission clears the window, it becomes payable in the brand's next payout run. Brands can also approve or deny a commission manually. 4. Refunds reverse the commission automatically, whenever they occur. Some brands also attribute server-side via the [S2S API](/docs/developers/s2s-tracking); same result, the brand's backend tells Rekomi which link name to credit. See [Earnings](/docs/affiliates/earnings) for what each status means. ## Multiple affiliates and one customer If the same customer clicks two affiliate links within the cookie window, attribution depends on the campaign's attribution model: - **Last touch** (the default): the most recent affiliate who drove a click wins. - **First touch**: the first affiliate who drove a click wins. The attribution model is listed on the brand's public campaign page; ask the brand if you are unsure which applies. ## Tracking your own analytics Want to know how many of your clicks turn into conversions in real time? The brand controls this. If they've wired their payment processor webhook (or the S2S API) into Rekomi, conversion attribution happens automatically and you see it on your [Performance](/docs/affiliates/earnings) page. If they haven't, ask them to wire it up (point them at the [S2S tracking API](/docs/developers/s2s-tracking) docs). --- # Your public page Source: https://rekomi.com/docs/affiliates/your-page > Claim rekomi.com/@you, publish instantly, and let brands book you directly. --- title: Your public page description: Claim rekomi.com/@you, publish instantly, and let brands book you directly. --- Your Creator page is your public media kit and booking page at `rekomi.com/@you`: your bio, your services with prices, and a "work with me" form brands use to reach you. It is free, it is live the moment you publish, and every deal it produces runs through Rekomi escrow so you always get paid. ## Claim your URL Head to **Creator page** in your dashboard (`/a/page`) and type the handle you want. Handles are 3 to 30 characters, lowercase letters and numbers with hyphens in the middle (no leading or trailing hyphen). If it is taken or reserved, the checker tells you straight away. Pick carefully: you can change your handle exactly once after claiming. When you do change it, links to the old handle stop working immediately, so update your link-in-bio everywhere. ## Publishing Publishing is instant. Once your page has your name and at least one verified social account, hit Publish and `rekomi.com/@you` is live; no waiting on a review. The verified social is what tells brands, and our reviewers, that the page is really yours: hit **Verify** on a platform row in the builder, or on the Socials card of your Creator page, and the account appears with a checkmark. A bio is recommended, not required. Separately, the Rekomi team reviews new pages in the background. Passing review adds two things: - A **Verified** badge on your page ("Reviewed and verified by Rekomi"), which brands trust. - A listing in the **creator directory** that brands browse to find people to book. Your page stays live during the review either way. Review also unlocks payable terms: brands can only send escrowed deal offers to reviewed creators, and deal links (proposals) unlock at the same time. There is also a separate **Website verified** badge, earned on your own, that shows you proved ownership of your website domain. Your website works like your social accounts: it only appears on your page once you have proved it is yours. Add it in the "Add your website" card on `/a/page` (the builder's Links section points there too), then prove the domain. When you signed up with an email on that same domain, one click verifies it instantly; otherwise you get three ways to prove it: add a DNS TXT record at `_rekomi.{domain}` with the value shown, paste a meta tag into your homepage's `` (the check reads `https://yourdomain.com/` itself, not a subpage), or put a small link back to your Rekomi page anywhere visible on your homepage (no code access needed). Once one is in place, hit Check now, and your website link joins your verified socials with the same checkmark pill. Moving to a different domain later starts the proof over; a different page on the same domain keeps it. ![A live creator page with the Verified badges, verified follower counts, the Next openings strip, services with prices, and the work with me form](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-public-page.png?v=3) ## What visitors see - Your photo, name, headline, and bio, with a Verified badge once reviewed. - Your topics, shown as chips. - Your verified social accounts with live follower counts. Verified pills are the only audience numbers on the page: there is no self-typed audience size, so the counts visitors see are ones Rekomi checked. - A link to your website, once you have verified the domain. - A **Next openings** strip with your open booking dates, if you publish any; brands can pick one when they fill in the form. - Your services with prices; clicking one opens the details and a "Request this service" button. Packages list exactly what is included (plus a "Save X%" pill if you set the savings badge), services with limited spots say how many, and booked-out services stay visible with booking paused. - Any custom sections you added in the builder (text sections, link lists, a past-partners logo wall), in the order you set. - The work with me form. Submissions land in [Brand deals](/docs/affiliates/bookings) and email you. - A note that payments and taxes on Rekomi deals are handled by Rekomi. Your page is indexed by search engines, so it can rank for your name. Only what you put on the page is public; the rest of your account (earnings, applications, messages) stays private. ![The Creator page after publishing: the live banner with your URL and the Brand deals doorway](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/creator-my-page.png?v=1) ## When a brand reaches out A brand fills in the form on your page: who they are, their budget, and what they have in mind. They can pre-select services straight from your list, and Rekomi estimates the total for them; if you publish open booking dates, they can also pick a preferred one, which shows on the request. The request appears in your [Brand deals](/docs/affiliates/bookings), and when you reply, the conversation continues on Rekomi (the brand chats from a private link, no account needed). From there you can send a proposal with your terms, and the deal moves into escrow: the brand pays up front, Rekomi holds the money, and you are paid when the work is delivered. The full flow is in [Deals and getting paid](/docs/affiliates/deals-and-payments). ## Design it your way The page builder at `/a/page/design` has 12 themes, 4 layouts, custom accent colors, background photos, and a Sections tab for reordering your page and adding your own sections. See [The page builder](/docs/affiliates/page-builder). --- # AI co-pilot Source: https://rekomi.com/docs/brands/ai-copilot > Growth+ feature. Customer-to-affiliate scan and outreach drafting on the Affiliates page, AI rate recommendations on each campaign's Commission tab. --- title: AI co-pilot description: Growth+ feature. Customer-to-affiliate scan and outreach drafting on the Affiliates page, AI rate recommendations on each campaign's Commission tab. --- The AI co-pilot is a set of AI features built into the workflows they inform, not a separate page. It requires the **Growth plan or higher** (trial accounts on Growth can use it during the trial). The features are powered by Anthropic Claude with a daily usage cap per organization. > There used to be a standalone `/dashboard/ai-copilot` page. It now redirects to `/dashboard/affiliates`, where the customer scan and outreach features live. Rate analysis lives on each campaign's **Commission** tab. ## What it does ### Customer scan (find affiliates among your customers) On the **Affiliates** page, open **Invite affiliates** and switch to the **From customers** tab. Click **Run scan** and the AI reads your Stripe customers, ranks them by how likely they are to make good affiliates, and returns a shortlist with a short reason for each pick. You check the candidates you want, pick the campaign, edit the invitation message, and send. Invites land in the normal Invites tab like any other invite. The scan runs on demand (nothing is scanned until you click the button), and you always decide who actually gets invited. Endpoints, if you prefer the API: `POST /api/ai/candidates` returns the ranked list, `POST /api/ai/invite` sends invites to the candidates you selected. ### Outreach drafting The AI can draft a personalized invitation email for a candidate in one of three tones: founder-direct, formal, or casual. You provide the recipient, the campaign name, and any context; it returns a subject and body for you to review and edit. Rekomi never auto-sends AI-drafted outreach. Drafts are drafts; you stay in control of what goes out. The endpoint is `POST /api/ai/outreach-draft`. ### Rate analysis Each campaign's **Commission** tab has an **AI rate recommendations** panel. Click **Run analysis** and the AI reviews the campaign's settings together with your workspace-wide conversion data and suggests rate adjustments, with reasoning and a recommended action where it has one. Nothing auto-applies: you review the suggestions and tune the tiers above the panel yourself. ![The campaign Commission tab; the AI rate recommendations panel is at the bottom](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-tiers.png?v=4) The analysis uses only your own organization's data: the campaign's settings plus your account-wide conversion statistics. There are no cross-organization benchmarks; your numbers are never pooled with other brands'. The endpoint is `POST /api/ai/commission-analysis`. ## Usage limits There is an org-wide daily cap on AI generations, shared across all three features. The default budget is **100 generations per day**, and the counter resets at **00:00 UTC**. Exceeding the cap returns HTTP 429 with a `Retry-After: 3600` header; try again later or the next UTC day. ## Privacy - The customer scan sends the model a truncated SHA-256 hash of each Stripe customer id, never the raw id. - The behavioral features sent per customer are: conversion count, total conversion amount, and first and last conversion timestamps. No payment methods, no Stripe API keys, no social handles or website URLs. - Any free text that reaches the model (for example customer-derived context) is fenced so it is treated as data, not as instructions. - Anthropic's API operates under their commercial terms; your data is not used for model training. ## When AI is not configured The co-pilot runs on a single platform-managed model key; there is no bring-your-own-key option per organization. If AI is not configured for the platform, requests still succeed (HTTP 200) but return empty results with an "AI not configured" note, and the UI shows an empty state instead of an error. --- # Broadcasts Source: https://rekomi.com/docs/brands/broadcasts > Send a single email to all (or filtered) affiliates. Plan limits and unsubscribe semantics. --- title: Broadcasts description: Send a single email to all (or filtered) affiliates. Plan limits and unsubscribe semantics. --- A broadcast is a single email you send to the affiliates in your organization. Use it for product updates, new asset drops, promo announcements, or asking creators to push a fresh campaign. ## Where to send broadcasts The composer lives on the campaign's Emails tab, under Broadcasts. ![The broadcast composer with subject, body, and campaign scope fields](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/broadcasts-composer.png?v=5) Form fields: - **Subject**: 1-200 chars. No newlines or control characters (server-side validated against header injection). - **Message**: 1 to 50,000 chars. Plain text. - **Campaign** (optional): scope the broadcast to one campaign's affiliates. Leave blank for all affiliates in the org. Personalize with variables like `{{affiliate.firstName}}`, `{{affiliate.link}}` (the same share link the affiliate copies from their dashboard) and `{{affiliate.code}}` (their active coupon code on the campaign) via the "Insert variable" menu on both the Subject and Message fields; each recipient sees their own values. Square brackets such as `[LINK]` are not variables and go out exactly as typed. Line breaks are kept, and lines that start with `- ` render as a bulleted list. ## Daily allowance and the overflow drip Every plan includes a number of bulk emails a day: 100 on Starter, 500 on Growth, 2,000 on Pro, and no limit on Enterprise. Accounts on trial, past due, or younger than 14 days send up to 100 a day. Transactional email (approvals, login codes, payout and tax notices) is never counted. A broadcast larger than what is left for today is not refused. Today's remaining allowance goes out now and the rest follows at your daily rate, in batches starting at 14:00 UTC on the following days, and the composer tells you the schedule before you send. The broadcasts list shows how many are still queued and when the next batch goes. Need more on a given day, for a launch or a time-sensitive announcement? Email support@rekomi.com and we can raise the allowance temporarily. Click "Review & send". The confirm step shows the recipient count before anything goes out: how many approved affiliates will receive it and how many unsubscribed addresses will be skipped. Click "Confirm send" to send. ## Addresses Rekomi skips Before each broadcast, affiliate invite or claim reminder goes out, Rekomi checks the recipient address. Addresses that do not exist, disposable addresses, spam traps, mailboxes with a history of marking mail as spam and marketplace relay addresses (order relay mailboxes that come in with a store import, such as `bounce-1234-seller@mail.codisto.com`) are skipped. Broadcasts and claim reminders also skip mailboxes with recent abuse reports or a very high fraud score. A skipped address is never handed to the email provider and never counts against your daily allowance, which protects your delivery rate and your sender reputation. Skipped addresses show on the Broadcasts tab under "addresses skipped", with the reason, and each broadcast shows a Skipped count next to Sent. The affiliates stay in your list; nothing else changes for them. If the check itself is unavailable for a moment, the email is sent as normal. ## What recipients see Each affiliate receives an email from `notifications@mail.rekomi.com` (brands with a verified custom sending domain send from their own domain instead; see [Sending email](/docs/brands/sending-domain)) with: - Your subject line - Your body - A `List-Unsubscribe` header (RFC 8058 one-click unsubscribe compliant) - A `Reply-To: support@rekomi.com` header (configurable) - The standard Rekomi-branded HTML wrapper The List-Unsubscribe header means Gmail and Yahoo show a one-click "Unsubscribe" button. Clicking it adds the recipient to the global suppression list. They will not receive broadcasts from any organization on Rekomi going forward. Transactional emails (payouts, application status) still arrive. ## Suppression list Three things land an affiliate on the suppression list: 1. They clicked List-Unsubscribe. 2. Their email hard-bounced (typo, full inbox, mail server rejected). 3. They marked an earlier broadcast as spam. Suppressed recipients are removed before the send, so the recipient count you see already reflects only deliverable addresses. The suppression list is global across organizations (we don't want different orgs spamming users who clearly want out). ## Plan limits Per-tier broadcast quotas (monthly, per recipient cap per broadcast): | Tier | Monthly emails (total recipient sends) | Max recipients per broadcast | |---|---|---| | Starter | 1,000 | 100 | | Growth | 10,000 | 1,000 | | Pro | 50,000 | 10,000 | | Enterprise | unlimited | unlimited | The composer enforces both limits at send time. If you exceed your monthly cap, the next broadcast is blocked with an HTTP 402 response and an inline error. ## Suspended broadcasts If your organization's rate of bounces plus spam complaints exceeds 5 percent over a 24 hour window (minimum 20 sends), broadcast sending is automatically suspended; the next send attempt returns an error explaining why. Resuming requires contacting support. This protects domain reputation for everyone on `mail.rekomi.com`. ## Rate limiting Broadcast endpoint is rate-limited per user (10 sends per minute). The idempotency-key header (optional) allows safe retries if your dashboard tab loses connection mid-send; the same `(orgId, idempotencyKey)` returns the prior result within a 60-minute window. ## Future: HTML compose, scheduled sends, segments Three features on the deferred list: - HTML composer with a WYSIWYG editor (today: plain text only) - Scheduled sends (today: send-now only) - Segment filters (e.g., "all affiliates who drove a sale in the last 30 days"; today: filter by campaign only) --- # Campaigns Source: https://rekomi.com/docs/brands/campaigns > The central object in Rekomi. Create, configure, archive. --- title: Campaigns description: The central object in Rekomi. Create, configure, archive. --- A campaign holds your affiliates, sales, commissions, and payouts. Most brands run one campaign. Some run multiple (per product line, per geography, per persona). ![The Campaigns page listing two campaigns, one paying per sale and one paying per sale plus per lead plus per click, with affiliates, sales, and payout figures](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaigns-list.png?v=10) Note on naming: the dashboard and these docs call this concept a **campaign** (matching industry vernacular). The REST API uses `/api/programs` for the same object, so API examples reference programs. ## Create a campaign From `/dashboard/campaigns`, click "New campaign". Required: ![The create-campaign dialog with the Sales, Leads and Clicks cards all selected, one rate block per leg, the deal read back in one sentence, and the invite-only notice](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-create.png?v=4) - `name` (visible to affiliates) - `slug` (URL-friendly identifier; used as the campaign's path on your portal, `{yourbrand}.rekomi.com/{slug}`; you can change it later and the old path redirects to the new one) - `referralDestinationUrl` (the website or page affiliates promote; Rekomi appends `?via={slug}` to it to build each affiliate's copy-ready link). Campaigns in one organization can point at different websites, so a second site on the same billing account usually just means a second campaign; a different payment-processor account or a different brand means a [second organization](/docs/brands/team#creating-another-organization) instead. Affiliates on sale-model campaigns can also create their own [product & page links](/docs/affiliates/deep-links) to other products or pages on the same site, up to 20 links per campaign including their main link; you can see every link they've created, with per-link clicks and sales, in the Performance tab's Links section (clicks only on pay-per-click campaigns, where the click itself is the paid unit; on Shopify stores links track clicks and sales with no setup, and coupon-code sales credit the affiliate's account as a whole). Leaving this URL empty turns the feature off, so set it early. - `defaultCommissionType` (Percentage or Fixed) - `defaultCommissionValue` (percent like 20, or a set amount like 10, paid as 10 units of the sale's currency: $10 on a USD sale, 10 EUR on a EUR sale; 0.50 for 0.50) - `defaultCommissionModel` (the campaign's main payout leg: Cps for per sale, Cpc for per click, Cpl for per lead; legacy Cpa/RevShare/Hybrid still accepted for existing campaigns; see [Commission models](/docs/brands/commission-models)). A campaign can pay for **any combination** of sales, leads and clicks: in the form, tick more than one option under "What do you pay affiliates for?"; via the API add `leadBountyCents` (per lead), `clickRewardCents` (per click) or `saleCommissionType` + `saleCommissionValue` (per sale on a click or lead campaign). See [Combine sales, leads and clicks](/docs/brands/commission-models#combine-sales-leads-and-clicks-on-one-campaign). - `cookieWindowDays` (typically 30, 60, or 90) - `minPayoutCents` (per-campaign minimum payout threshold; defaults to $50) Optional at creation: - `description` (shown on the public application page) - `termsUrl` (link to your affiliate terms; settable via the API today, shown on the public landing page and invite accept page) - `attributionModel` (FirstTouch or LastTouch; default LastTouch) - `linkSlugStyle` (Random or FirstName; default Random; see [Affiliate link codes](#affiliate-link-codes) below) - `autoApprove` (boolean; if true, applications are approved on submit). **Not available on invite-only campaigns** (any campaign that pays per click, or pays nothing per sale): auto-approve and the public application page are turned off automatically. - Creator network listing and search-engine listing toggles (whether the campaign appears in the Rekomi network directory and whether its public signup page is indexable). New campaigns start private. Turning the network switch on needs an active paid plan and a funding source for payouts on your account (a bank debit on file or bank-transfer funding enabled); until then it cannot be turned on and says why. See [Apply to the curated network](/docs/brands/network). ## The campaign detail page Once created, `/dashboard/campaigns/{id}` has nine sections in a left rail (a horizontal strip on small screens): **Overview** (the default view), **Affiliates**, **Performance**, **Commission**, **Products**, **Coupons**, **Promo materials**, **Emails**, and **Campaign settings**. Overview is a summary dashboard for the campaign. Affiliates holds your roster plus a Recruiting section for sub-affiliate setup. Performance groups Activity, Links, Sub-IDs, Leads, and Fraud into sections of one tab (the Leads section appears once the campaign has leads, or while it has no sales yet; stores connected through Shopify never see it). Sub-IDs lists the channel tags affiliates add to their links (`?via=alex&sub=yt`) with clicks, leads, sales, commission and earnings per click for each tag and affiliate; see [Sub-IDs](/docs/affiliates/tracking-link#sub-ids). Commission holds the campaign's rate configuration, Products holds the catalog and per-product rates, and Coupons holds coupon-code attribution. All three show on every campaign; where one doesn't apply (per-product rates on a pay-per-click or pay-per-lead campaign, coupons on a billing provider without coupon support), the tab explains what the feature does and what unlocks it instead of hiding. Promo materials holds the banners and ready-made posts you upload for affiliates. Emails holds the automated lifecycle-email switches for this campaign plus the broadcast composer (see [Emails](/docs/brands/emails)). The header shows your public signup page URL with Copy, Open, and **Edit signup page** actions beside it; the Edit button opens the signup page editor. Inside Overview, the **Activity** view is the tracking check for that campaign. It shows what Rekomi has seen in the last 30 days as four counters: clicks, leads recorded (emails your site captured after an affiliate click), purchases your connected payment gateway delivered (every customer, credited or not), and sales credited to affiliates. Purchases with no leads is the signature of a hosted checkout whose email capture is not running; the panel says so and points at the install guide. On beehiiv, Kit, MailerLite, Ontraport and GoHighLevel a purchase with no lead can still be credited, because the platform connection looks the buyer up on the platform as the last step; sales credited that way are counted separately in the same view. Below the counters, **Check a buyer's email** tells you for one address whether Rekomi saw a lead, a purchase, a credited sale, or nothing, so you can test an install without a support ticket. Plus-addressed variants of one inbox (you+test@gmail.com and you@gmail.com) are treated as the same person. ![The campaign detail page with the signup page link and Edit signup page button in the header, the tab bar, and the campaign KPI cards](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-detail.png?v=9) (The **Commission** tab hosts the default commission display, partner groups (rate tiers you assign partners to in bulk), and the performance tier editor. For campaigns that pay per click or per lead it also shows the live budget spent/remaining meter; the budget cap itself is set on the campaign form in **Campaign settings**. The **Performance** tab's Fraud section hosts the fraud review queue.) ### Affiliates Approved and pending affiliates for this campaign. Approve, reject, edit commission overrides per affiliate. A Recruiting section on this tab configures sub-affiliate recruiting; see [Recruiting (sub-affiliates)](/docs/brands/sub-affiliate-recruiting). ### Commission Default commission display (set in Campaign settings), partner groups for assigning rate tiers to affiliates in bulk, and the performance-based tiered commission editor (see [Commission models](/docs/brands/commission-models)). A Product commission rates card is also available on the Products tab on every sale campaign, letting you set a commission rate for a specific product; the rate applies wherever the payment provider reports what was bought: Shopify, Stripe, Gumroad, Lemon Squeezy, Polar, Creem, Dodo, Mollie (when the payment carries order lines), and Braintree subscriptions, ahead of the affiliate's base rate (everything else uses the campaign rate). See [Per-product commissions](/docs/brands/product-commissions). ### Promo materials Uploads of promotional material for your affiliates: banners and ready-made posts (swipe copy) you add here become visible to approved affiliates from their campaign page. See [Creative assets](/docs/brands/creative-assets). ### Campaign settings Edit all campaign fields, organized into section pills (General, Commission, Payouts, Tracking, Applications, Visibility, and CRM sync) that show one group at a time: ![The Commission section of campaign settings with the three payout legs selected and a rate block for each](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-settings.png?v=5) - `name`, `description` (`termsUrl` is editable via the API only today) - `defaultCommissionType`, `defaultCommissionValue`, `defaultCommissionModel` - `cookieWindowDays` - `recurringMonths` (cap recurring payouts; not set/null = lifetime, 0 = one-time, N = each customer's first N calendar months from their first payment) - `recurringDelayInvoices` (skip the first N invoices before paying commission, useful for trial-heavy products) - `budgetCents` (campaigns that pay per click or per lead; optional cap on click and lead spend, sale commissions are never capped; see [Pay per click or lead](/docs/brands/cpc-and-cpl)) - `leadBountyCents`, `clickRewardCents`, `saleCommissionType` + `saleCommissionValue` (the other payout legs a campaign can add to its main one; the form's "What do you pay affiliates for?" multi-select edits all three, see [Combine sales, leads and clicks](/docs/brands/commission-models#combine-sales-leads-and-clicks-on-one-campaign)) - `attributionModel` (FirstTouch or LastTouch) - `linkSlugStyle` (Random or FirstName; see [Affiliate link codes](#affiliate-link-codes) below) - `autoApprove` (toggle) - `minPayoutCents` (per-campaign minimum payout threshold; affiliates accrue until their balance reaches it, and it applies on top of your account-wide minimum) - Creator network listing and search-engine listing toggles (control whether the campaign appears in the Rekomi network directory and whether its public signup page is indexable). The network switch cannot be turned on, and a note says why, until your account has an active paid plan and a funding source for payouts. A campaign that is already listed stays listed: creators stop seeing it while either is missing, the switch shows a paused note, and it shows again on its own once both are back. - Traffic from ads policy: what happens to a sale attributed to an ad-flagged click (an affiliate running paid ads to your site). **Allow** (the default) counts ad-driven sales like any other; **Hold for review** parks them in your fraud queue until you approve them; **Block** pays no commission on them. - Public leaderboard ("Show a leaderboard to affiliates"): per campaign, off by default. When on, the affiliate campaign page shows the top earners with other affiliates' names masked; each affiliate sees their own row unmasked. - Countries that can join (`allowedCountries`): limit which countries affiliates may apply from. All countries are allowed by default; pick a subset in the searchable country list (under "Show more options" when creating a campaign, or in Campaign settings under Applications) and three things happen on your public signup page: visitors located outside the allowed countries see a "not available in your country yet" notice instead of the application form (based on their IP-derived location), the form's country picker only offers the countries you allow, and the server refuses applications whose country or location falls outside the list. Selecting every country (or clearing the field) returns the campaign to open-to-everyone. - `customSubdomain` (every plan): publish your portal on a domain you own (e.g. `affiliates.yourbrand.com`) instead of the free `{yourbrand}.rekomi.com`. Each campaign keeps its clean path (`affiliates.yourbrand.com/{slug}`). See [Settings → Custom affiliate-portal subdomain](/docs/brands/settings#custom-affiliate-portal-subdomain) for the DNS-verification flow. The URL slug can be renamed from the **Campaign URL** group in the signup page editor's Content tab (open it with **Edit signup page**). The old path automatically redirects to the new one, so links your affiliates already shared keep working. ### Affiliate link codes Every affiliate has a link code, the part of their referral link after `?via=`. The **Affiliate link codes** setting in the Tracking section decides what a brand-new affiliate's code looks like when they join this campaign. **Random code** (the default) gives them a generated code. **Affiliate's first name** builds the code from the first name they applied with, so their link reads like `yourstore.com/?via=olivia`. When that name is already taken the code adds their last-name initial, and when that is taken too it adds a number. Every candidate is checked against Rekomi's reserved words and against every link code on the platform, not only your campaign, because an affiliate's code is shared across every brand they work with. The setting only shapes the code an affiliate starts with. Affiliates who already have a Rekomi code keep it when they join you, and any affiliate can still change their own code afterwards from their settings. ## Recurring commission settings For subscription products, the default commission applies to every renewal for the life of the subscription. The campaign form's "How long affiliates earn per customer" control sets this; the underlying field is `recurringMonths`: - not set / lifetime (default): pay on every renewal as long as the subscription stays active. - `recurringMonths = 0`: one-time. One commission per new customer: their first subscription payment pays, renewals do not, and a cancel and resubscribe does not pay again. One-off orders with no subscription (a store checkout) always pay. - `recurringMonths = 12`: pay on every payment in each customer's first 12 months, counted from that customer's first payment, then stop. On a monthly plan that is 12 payments; on a yearly plan it is the first payment only. Months are calendar months, so a mixed monthly and yearly catalogue needs one setting, not two. The window belongs to the customer: a cancel and resubscribe, or a plan change that replaces the subscription, stays inside the same 12 months. - `recurringMonths = 3`: pay on the payments in each customer's first 3 months. `recurringDelayInvoices` lets you skip the first N invoices. Common pattern: trial-to-paid products often refund the first invoice if a customer churns, so setting `recurringDelayInvoices = 1` reduces clawback exposure. ## Archive Archiving hides the campaign and stops its links from accepting new clicks and sales. Existing records stay for historical reporting. Archiving cannot be undone from the dashboard today; contact support if you need a campaign restored. There is no hard delete. Via the API this is `DELETE /api/programs/{id}` (Owner role). ## Public landing page Every active campaign has a public landing page on your branded portal: your primary campaign at `{yourbrand}.rekomi.com/` and every other campaign at `{yourbrand}.rekomi.com/{slug}`. On every plan, the same pages can serve from your own domain (`affiliates.yourbrand.com/{slug}`). Out of the box the page shows your logo, the campaign name and description, live stats (commission, cookie window, attribution model), a how-it-works section, the terms link, and the application form. The **Landing page** tab opens a full editor with layout options (centered, split, or hero banner), one-click theme presets including dark palettes, 1,900+ fonts, and content blocks including a product showcase; see [Recruit affiliates](/docs/brands/recruit-affiliates) for the full tour. --- # Commission models Source: https://rekomi.com/docs/brands/commission-models > How Rekomi's commission tracking works, percentage vs flat fee. Per sale (one-time, set period, or lifetime recurring), per click, and per lead. Plus tiered commissions. --- title: Commission models description: How Rekomi's commission tracking works, percentage vs flat fee. Per sale (one-time, set period, or lifetime recurring), per click, and per lead. Plus tiered commissions. --- Rekomi gives you two commission **types** (how the math works) and a small set of commission **models** (when a commission triggers). They combine to cover every common affiliate payout pattern. ## Commission types | Type | Math | Example | |---|---|---| | **Percentage** | A percent of the invoice amount | 25% of every $99 invoice = $24.75 | | **Fixed** | A set amount per sale, in the currency the sale arrives in | 50 per paid invoice ($50 on a USD invoice, 50 EUR on a EUR invoice) | You set the type and value at the campaign level (`defaultCommissionType`, `defaultCommissionValue`). You can override the type and value per-affiliate (custom commission) and per-tier (performance tiers). A Fixed rate is a number, not a dollar figure. It pays that many units of whatever currency the sale arrives in, so a rate of 50 pays $50 on a USD invoice and 50 EUR on a EUR invoice. If you sell in several currencies, choose Percentage so the payout stays proportional everywhere. Because a flat 50 is worth very different amounts in different currencies, a Fixed-rate sale that arrives in a currency other than your home currency is flagged to Rekomi's team (once a day per workspace) so an unintended magnitude is looked at the same day; the commission itself still pays as the rule says, nothing is withheld or re-priced automatically, so check the campaign's rate if you start selling in a new currency. ## Commission models > **Per sale is live for every brand today.** Paying per click or per lead needs a Starter plan or above, and a campaign that pays per click, or pays nothing per sale, is invite-only. Every click and lead runs through the de-duplication, real-time IP fraud-scoring, and aggregation pipeline described below before it can be paid, with an optional per-campaign budget cap. You can also **combine** the three on one campaign; see [Combine sales, leads and clicks](#combine-sales-leads-and-clicks-on-one-campaign). The campaign form calls the three payout legs **Sales**, **Leads** and **Clicks**; the API's `defaultCommissionModel` field names the campaign's main one. Each leg triggers on its own event: - **Sales** (pay per sale, also called CPS, cost per sale): pay when a referred customer makes a purchase. Choose a **flat fee** (paid in the currency the sale arrives in, as described above) or a **percentage**, then choose how long the affiliate keeps earning on that customer (see [Recurring payouts](#recurring-payouts)): **one time**, **a set period** of months, or the customer's **lifetime**. This one model covers both one-off sales and subscription revenue share. (RevShare was the old name for a recurring per-sale percentage; existing RevShare campaigns now appear as **Per sale**, percentage, lifetime, with identical payouts.) - **Leads** (pay per lead, also called CPL, cost per lead): pay a flat fee per qualified lead. The affiliate shares a Rekomi-hosted lead form (`/lead/{slug}`); each submission passes an anti-bot challenge (Cloudflare Turnstile), email-format + disposable-domain validation, a self-lead guard, and per-affiliate de-duplication before it earns. Use for free-trial signups, demo requests, and waitlists. - **Clicks** (pay per click, also called CPC, cost per click): pay a flat fee per validated click. Campaigns that pay per click use a neutral Rekomi tracking link (`rekomi.link/{slug}`) so the click is measured on Rekomi's side, not your server, the affiliate can't be under-counted. Clicks are de-duplicated per device, bot/anonymizer-filtered, and aggregated into commissions hourly. Rare in subscription contexts; useful for paid content or syndication deals. (Pairs with the [fraud notes](#fraud-and-quality) below.) Most subscription products use **Sales**, percentage, set to **lifetime** recurring, with a 20-30% commission. > **Legacy models.** CPA (cost per action) and Hybrid were earlier model names, retained in the API for existing campaigns (they behave like Per sale). They are not offered when creating a new campaign; use **Sales** instead. ## Combine sales, leads and clicks on one campaign A campaign can pay for any combination of the three: sales only, sales plus leads, leads plus clicks, all three, and so on. In the campaign form, **What do you pay affiliates for?** is a multi-select: tick **Sales**, **Leads** and **Clicks** in any combination and set a rate for each. The same block lives in Campaign settings under **Commission**, and the form reads the whole deal back in one sentence ("Affiliates earn 20% of every payment, for the customer's lifetime, plus $5.00 per qualified lead, plus $0.10 per validated click"). How each leg pays: - **Sales** pay the commission on an attributed purchase, with the commission length you choose (first payment only, a set number of months, or the customer's lifetime). Refunds claw the commission back the standard way. - **Leads** pay a flat amount, in your home currency, for each qualified lead sent through the campaign's Rekomi-hosted lead form. Every submission passes the same anti-bot, disposable-email, self-lead and de-duplication checks as a lead-only campaign, and a high-risk lead is quarantined for your review instead of paid. - **Clicks** pay a flat amount, in your home currency, for each validated click on the affiliate's Rekomi tracking link (`rekomi.link/{their-slug}`). Clicks are fraud-scored, de-duplicated per device and day, and capped per IP and per affiliate before anything is paid. A visitor counted at the link and then counted again by your site's tracking script pays once. The rules that come with combining them, so you know exactly what you are agreeing to pay: - **Paying per click makes the campaign invite-only.** So does paying nothing per sale (a lead-only or click-only campaign). Public sign-up, auto-approve and the creator-network listing switch off, and you invite each partner and set their rate. A campaign that pays per sale, with or without a lead reward, can stay public. - **Per-click and per-lead pay need a Starter plan or above**, whatever else the campaign pays. - **The campaign budget caps click and lead spend only.** Once the campaign's click and lead rewards reach the cap, new clicks and leads stop earning. Sale commissions pay on real revenue and are never counted against the cap. - **Partner-specific rates apply to one leg.** Invite rates, partner groups, performance tiers and coupon rates override the campaign's main rate: the sale commission on a campaign that pays per sale, the per-click amount on a click campaign, the per-lead amount on a lead campaign. The other legs pay the same flat amount to every partner, and the form tells you which leg is which. - **Click and lead rewards are never refunded.** There is no invoice to reverse, which is why they are validated before payment rather than clawed back later. Affiliates see every leg on their invite, on their campaign page and in their earnings ledger. A campaign that pays per click shares the Rekomi tracking link, which counts the click at Rekomi's edge and then sends the visitor on to your site with the affiliate's code, so sales through it still credit them. A campaign that pays per lead alongside sales or clicks also shows the affiliate a lead-form link. For API and MCP callers, `defaultCommissionModel` names the campaign's main leg (`Cps`, `Cpc` or `Cpl`) and `defaultCommissionType` / `defaultCommissionValue` carry its rate. `leadBountyCents` adds a per-lead amount to a sale or click campaign, `clickRewardCents` adds a per-click amount to a sale or lead campaign, and `saleCommissionType` / `saleCommissionValue` add a sale commission to a click or lead campaign (its length follows `recurringMonths`). See the [API reference](/docs/developers/reference). ## Fraud and quality Because paying per click and per lead (CPC and CPL) rewards an action with no purchase behind it, Rekomi filters those clicks and leads before any commission is minted: - **Neutral measurement.** Paid clicks are counted at Rekomi's edge (`rekomi.link`) before the visitor reaches your site, so the number you pay on isn't controlled by either party alone. - **De-duplication.** At most one payable click per device per day per affiliate; at most one paid lead per (campaign, email), including plus-aliases (keyed on the campaign, not the affiliate, so two affiliates cannot both be paid for the same person). - **Bot + anonymizer filtering.** Cloudflare bot score and (when enabled) datacenter/VPN/proxy detection drop low-quality clicks. The lead form adds a Cloudflare Turnstile challenge, email-format + disposable-domain validation, and a self-lead guard. Click and lead rewards (CPC and CPL) are **not refundable** the way sales are, there is no invoice to reverse, so quality is enforced up front rather than clawed back later. To stop a bad actor, reject or ban the affiliate from the Affiliates tab. Paying per click or per lead requires a **Starter plan or above**, on any campaign shape. ## Tiered commissions You can override the default with **performance tiers** based on revenue brought in by each affiliate. Each tier has: - `minRevenueCents`: the threshold (cumulative referred revenue) where this tier kicks in, in minor units of your home currency (the tier editor shows it in that currency) - `commissionType`: Percentage or Fixed - `commissionValue`: the percent, or the set amount in the sale's currency The highest-matching tier wins. Tiers are configured at `/dashboard/campaigns/{id}` in the Commission tab. Example: - Tier 0 (default): 20% recurring - Tier 1 (after $10,000 referred): 25% recurring - Tier 2 (after $50,000 referred): 30% recurring An affiliate who has driven $12,000 in referred revenue would fall into Tier 1 (25%). ![The Commission tab of a campaign that pays per sale, per lead and per click: the default commission sentence, the budget card, partner groups, and the performance tier editor](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-tiers.png?v=5) Tier rates apply automatically, on every live rail, including sales reported through the [S2S API](/docs/developers/s2s-tracking). At each sale, the affiliate's cumulative referred revenue in the campaign selects the highest tier they have crossed, and that rate is used for the sale (never retroactively re-pricing older sales). A per-affiliate override or partner group rate still wins over any tier. When sales arrive in more than one currency, the ladder reads like this: - Thresholds are typed in your home currency. A sale in your home currency is compared against the threshold as written. - A sale in another currency is compared against the threshold converted into that currency at the reference rate for the day of the sale. A $1,000 threshold on a USD workspace is 147,000 JPY at 147 yen per dollar and C$1,360 at 1.36, so a JPY affiliate reaches Tier 1 once their JPY sales pass 147,000 JPY, not 1,000 JPY. - If Rekomi holds no rate from the last 7 days for that currency, the sale pays the untiered rate (the affiliate's own override or group rate, or the campaign default) rather than a tier picked at the wrong number, and our team is alerted once a day per workspace so it is put right when rates resume. - Ladders are per currency: an affiliate's revenue in one currency counts only toward their tier in that currency. Their EUR sales never qualify their USD sales, and the other way round. ## Partner groups Assign affiliates to a named rate group from their detail page to manage rates in bulk; groups live in the same Commission tab as the tier editor. A partner's own custom rate beats their group rate; a group rate beats any performance tier. ## Per-affiliate overrides You can override the commission for a specific affiliate without changing the campaign default or the tiers. From the Affiliates tab on a campaign, click an affiliate, and set their custom commission. Use this for partnerships you negotiate one-off (a marquee creator, an exclusive deal, an introductory offer). ## Per-product rates On rails that report what was bought (Shopify, Stripe, Gumroad, Lemon Squeezy, Polar, Creem, Dodo, Mollie when the payment carries order lines, and Braintree subscriptions), you can also set a commission rate for a specific product from the Products tab's Product commission rates card. This slots into the precedence chain within a sale: the affiliate's base rate resolves first (per-affiliate override, then group, then tier, then campaign default), and then, line by line, any line matching a product with its own rate uses that product rate instead, while the rest of the sale's lines still use the affiliate's base rate. See [Per-product commissions](/docs/brands/product-commissions). The same Products tab also holds **Assigned partners**: a product assigned to a partner is paid to that partner on every sale, whatever link, coupon or click brought the buyer, and that sale is then priced by the ladder above (product rate first, else override, group, tier, or campaign default). Unassigned products in the same order attribute as usual. See [Assigned partners](/docs/brands/product-commissions#assigned-partners). One rate sits above all of these: a [coupon's own commission rate](/docs/brands/coupons#per-coupon-commission-rates). When a sale is attributed to a coupon code whose coupon (or the choice the affiliate picked) carries a commission rate, that rate wins outright, over per-affiliate, group, tier, campaign default, and per-product rates alike, and the per-product blend is skipped for that sale. It is the most specific statement of intent there is: you set what that exact code pays, and (for choice coupons) the affiliate actively picked it. Recurring caps and delay windows still apply on top; a coupon rate never revives a capped or delayed recurrence. ## Give a free month instead of cash For a refer-a-friend campaign whose referrers are your own customers, the Sales leg can reward with a **free month on your product** instead of a cash commission. When a referred friend pays their first invoice, Rekomi applies a credit on your own billing platform, sized from the referrer's plan (or a fixed amount you set), and it comes off their next invoice. Referrers never need a payout method or a tax form, and these campaigns pay for sales only, once per new paying friend. Works on Stripe (through the Rekomi app for Stripe), Chargebee, Recurly, Paddle, Braintree, Lemon Squeezy, Dodo Payments and Mollie, Starter plan and above. On Shopify, Wix and BigCommerce the same switch gives **store credit** instead: the commission rate (or a fixed amount) on every referred order, added to the affiliate's store account or sent as a Wix gift card. See [Free month and store credit rewards](/docs/brands/free-month-rewards). ## What about refunds Commissions are not paid on refunded invoices. A partial refund claws back only its proportional share of the commission (a 40% refund claws back 40% of the commission), and the sale only moves to `Refunded` status once cumulative refunds cover the full invoice; a full refund reverses the whole commission right away. If the commission had already been paid out, the refunded share is owed back and the affiliate's next payout deducts it automatically; your workspace admins are notified, and you can also reverse that commission on Stripe from the payout's Detail page (see [Refunds after a payout](/docs/brands/payouts#refunds-after-a-payout)). See [Tracking and attribution](/docs/brands/tracking-and-attribution) for refund timing details. ## Recurring payouts For **Per sale** campaigns, the Commission section of the campaign form has a "How long affiliates earn per customer" control with three choices: - **Lifetime**: earn on every renewal, forever. The default. - **For a set period**: earn on every payment in each customer's first N calendar months, counted from their first payment, then stop. Twelve months is twelve months whether the customer pays monthly or yearly. - **One time**: one commission per new customer. Their first subscription payment earns; renewals do not, and a cancel and resubscribe does not earn again. One-off orders with no subscription (a store checkout) always earn. How the set period is measured, in detail: the clock starts on the date Rekomi recorded the customer's first successful payment with you, whichever subscription or order it came through (the payment date, not the service period the payment covers), and every later payment by that customer dated inside the window earns commission (a skip-the-first-N-invoices setting, below, still applies on top of the window). The window closes three days before the N-month boundary, so a renewal your billing platform charges a little early for the next period is not paid by accident. With 12 months set, a monthly subscriber earns on their first 12 payments (the thirteenth, due at the anniversary, falls outside), and a yearly subscriber earns on the first annual payment only, because the anniversary renewal lands on the boundary. Plan changes on the same subscription (an upgrade, downgrade, or a prorated charge for the change) are further payments on that subscription: they earn commission on the pre-tax amount of that charge while the window is open, and they never restart the clock. The window follows the customer, not the subscription: if a referred customer cancels and later comes back under a new subscription, or a plan change replaces their subscription with a new one, they stay inside the same window, and once it has closed nothing further pays that affiliate for that customer. This applies to one-off orders too: a returning customer's repeat orders earn commission only inside their first N months. Rekomi recognises the returning customer by the customer id your billing platform sends with each subscription payment; a payment that carries no customer id falls back to the subscription id, and a one-off order that carries neither (a guest checkout, for example) is treated as that customer's first payment. If the customer later buys through a different affiliate's link or code, that affiliate's own window starts from the first payment attributed to them. Two campaign fields back this control: - `recurringMonths`: **not set (null) = lifetime** (pays every renewal); **`0` = one time** (no renewals paid); **N = each customer's first N calendar months**. (The control appears whenever the campaign pays per sale, including a click or lead campaign with a Sales leg; it is hidden on campaigns that pay nothing per sale.) - `recurringDelayInvoices` (default `0`): skip the first N invoices before paying renewal commission. Useful if your product has a high first-invoice refund rate or a discounted first month. These settings live on the campaign, not on the commission tier. --- # Coupon-code attribution Source: https://rekomi.com/docs/brands/coupons > Use Stripe promotion codes as a tracking mechanism alongside referral links. --- title: Coupon-code attribution description: Use Stripe promotion codes as a tracking mechanism alongside referral links. --- Coupon-code attribution lets your affiliates share a redeemable string, essentially a referral code, at checkout instead of a click-tracked URL. Useful when: - Your affiliate posts in their podcast, video, or newsletter and viewers will type a code rather than click. - The customer hits your site directly (search, brand awareness) but redeems a code at Stripe Checkout. - You want a paper trail in Stripe that ties revenue back to a specific affiliate. Rekomi creates the Stripe coupon for you, mints a unique promotion code per affiliate, and credits the right affiliate whenever a customer redeems one at checkout. Refunds clawback automatically. Coupon attribution works on the Stripe, Shopify, Wix, BigCommerce and WooCommerce integrations (on a BigCommerce multi-storefront store, a code minted under a campaign limited to certain storefronts only works on those storefronts) (codes are real promotion codes or discount codes in that checkout, so customers actually get the discount). Stripe codes are authored from this dashboard; Shopify, Wix and BigCommerce codes are authored from the embedded Rekomi app inside the store admin and listed here; WooCommerce codes are authored here and created by the plugin. It is not available on other billing providers (Paddle, Lemon Squeezy, Chargebee, and the rest) or on server-to-server-only setups; on those, the campaign coupons area in your dashboard explains this instead of offering the feature, and affiliates keep earning through their links. Affiliate self-mint (affiliates creating their own vanity codes) works on every coupon rail. ![The campaign Coupons tab with a campaign coupon showing codes minted, self-serve mode, and click-override state](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-coupons.png?v=5) ## How it works 1. **Brand creates a campaign coupon.** Pick a discount (% off or fixed $ off), a duration, optional max redemptions and expiry. Rekomi creates the matching Stripe `coupon` under the hood and stores its id. 2. **Brand mints a per-affiliate code.** From an affiliate's detail page, click "Generate" in the Coupons tab. Rekomi creates a Stripe `promotion_code` that points at the campaign coupon and is tagged with the affiliate's id in metadata. 3. **Affiliate shares the code.** The code shows on `/a/campaigns/{id}` for them to copy. Optionally, brands let affiliates mint their own vanity codes from /a: each campaign coupon has a self-service setting with three modes (no one, all approved affiliates, or a specific allow-list of affiliates you pick). 4. **Customer redeems at checkout.** Stripe fires `invoice.paid` or `customer.subscription.created` with the promotion code embedded. Rekomi sees it, credits the owning affiliate, records a conversion with `attribution_method = "coupon"`. 5. **You see it like any other sale.** The conversion lands in your dashboard automatically, tagged in the **Source** column as "Coupon" with the redeemed code (for example "Coupon · SARAH20"), right alongside link and server-to-server conversions. No setup needed: coupon conversions are never silent. The same `attribution_method` (and the code) is in the [REST API](/docs/developers/reference), the CSV export (`attribution_method` + `coupon_code` columns), and the [`conversion.created` webhook](/docs/developers/webhooks) if you sync conversions to your own systems. 6. **Refunds clawback.** A full refund decrements the affiliate's redemption count and marks the conversion refunded; partial refunds claw back commission proportionally and keep the redemption counted. ## When coupon beats click attribution Each campaign coupon has an `Overrides click attribution` toggle (default on). When on, a coupon redemption credits the coupon's affiliate even if the customer also clicked another affiliate's tracking link. Use this when: - You want code-based affiliates (influencers, podcast hosts) to win over generic site traffic. - You want a deterministic, observable attribution path with no cookie ambiguity. - Your affiliates drive purchase intent through audio/video where clicks are rare. Turn it off when: - Click tracking is your primary channel and codes are secondary. - You run multiple-affiliate-touch flows where the first click should win and the coupon is a "thank you for redeeming" mechanic. ## Multiple codes per affiliate You can mint as many codes as you like for one affiliate. They all roll up to the same affiliate's stats; each code has its own redemption counter. Useful for: - Campaign-specific codes (`SARAH-BLACKFRIDAY` vs `SARAH-SPRING`). - A/B testing copy ("Try `SARAH20`" vs "Try `SARAHSPRING`"). - Letting the affiliate brand variants without losing attribution. ## Limit a coupon to specific products By default a campaign coupon discounts the whole order. You can scope it to specific products instead, so the discount (and the attribution) only applies to those line items. On the Stripe rail, use "Limit to specific products" in the coupon create dialog: a picker lists your active Stripe products (name, image, and id) with search, so you choose rather than type. The picker shows your 100 most recent active products; for deeper catalogs, or if you prefer, "Paste product IDs instead" accepts comma-separated `prod_...` ids, and the API takes the same `productRefs` field (see [Coupon code tracking](/docs/developers/coupons)). On a Shopify store, use "Limit to products" when creating a coupon offer in the Rekomi app inside your Shopify admin: pick the products with Shopify's own product picker, and every code minted under that offer inherits the scope. ## Plan tier Coupons are available to every brand from the dashboard. Managing coupons through the public API requires a paid plan (Starter or above). Trial users (14-day free trial on any tier) can use everything. ## What gets created in Stripe For every campaign coupon Rekomi creates: - A Stripe `coupon` with the discount you picked, optional `redeem_by` (expiry), and `metadata.rekomi_campaign_coupon_id` matching the Rekomi row id. For every per-affiliate coupon Rekomi creates: - A Stripe `promotion_code` attached to the campaign coupon, with the code string you supplied (or one we generated), optional `max_redemptions` inherited from the parent, and `metadata.rekomi_affiliate_coupon_id` matching the Rekomi row id. If you delete a Rekomi row, Rekomi deactivates / deletes the Stripe object on a best-effort basis. The Rekomi row is the source of truth for attribution. ## API access Coupon endpoints are documented in the [developer reference](/docs/developers/coupons). Both `/api/campaigns/{programId}/coupons` and `/api/affiliates/{affiliateId}/coupons` are exposed under `/api/v1/*` for public-API callers with `rk_live_*` keys. ## Affiliate self-service Each campaign coupon has a self-service mode that controls who may mint their own codes: - **All approved affiliates** (the default for new coupons created in the dashboard or the Shopify app): anyone approved on the campaign can mint vanity codes from `/a/campaigns/{id}`. Self-serve is the point: you define the offer, affiliates handle their own codes. - **No one**: only you mint codes, from the affiliate detail page. (This stays the default for API callers that omit the field, so existing integrations keep their behavior.) - **Specific affiliates**: an allow-list you pick, in the dashboard or the Shopify app; removing an affiliate from the list revokes the codes they minted. Codes still need to satisfy `^[A-Z0-9_-]{3,32}$` and be unique within the brand. Self-service is useful for influencer programs where each creator controls their personal brand string, or hands-off flows where you'd rather not gate every mint behind brand-side approval. You can change the mode at any time; codes already minted keep working unless you revoke them. ## Per-coupon commission rates A campaign coupon can carry its own commission rate. When you set "Codes from this coupon earn" on a coupon, every sale made with one of its codes pays that rate instead of the campaign rate (and instead of any per-affiliate, group, tier, or per-product rate). Zero is a real setting: "codes earn nothing" is how you run a pure-discount code, for example a Black Friday code that should not pay commission. ![The coupon create dialog with the optional codes-from-this-coupon-earn field](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/coupon-override-earn.png?v=3) Two honest details: - The rate applies to sales made **with the code**. If the same affiliate shares their tracking link instead, link sales pay the normal campaign ladder. - On subscriptions, Stripe attaches the code to each invoice it discounts. A "once" coupon pays the coupon rate on the first invoice; renewals (which no longer carry the discount) revert to the campaign ladder. "Forever" and still-running "repeating" coupons keep paying the coupon rate on every discounted invoice. ## Affiliate choice: let each affiliate pick their split Some programs give ambassadors a fixed benefit and let each person decide how to use it: gift the whole discount to their audience, or share it and keep a commission. The **Affiliate choice** toggle in the coupon create dialog does exactly that. You define 2 to 4 choices, each a pair of buyer discount and affiliate commission, for example: - Choice A: buyers get 10% off, the affiliate earns nothing (the gift option). - Choice B: buyers get 5% off, the affiliate earns 5% per sale. When an affiliate creates their code, they pick one choice; the pick decides both the discount their buyers get and what their code pays them, and each code shows its own economics ("5% off · earns you 5% per sale"). The dialog previews the exact options affiliates will see, generated from your numbers, so nobody has to write or proofread the copy. ![The coupon create dialog with Affiliate choice on, two sentence rows, and the live preview of what affiliates see](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/coupon-choice-editor.png?v=2) When an affiliate creates their code, the same options render in their mint flow: ![The affiliate mint flow showing the how-should-your-code-work choice cards](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/affiliates/coupon-choice-mint.png?v=1) Rules worth knowing: - Choice coupons default to **5 codes per affiliate** (adjustable in the coupon's settings, including down to 1). Each code locks in the choice it was created with, and every sale pays by the code that was used; past sales keep the rate they were earned under. - Choices are immutable after create, like the discount itself. Archive the coupon and create a new one to change them. - On subscriptions, the same renewal rule as above applies: the chosen split pays on the invoices the code discounts. A "once" coupon pays it on the first invoice; renewals revert to the affiliate's usual rate. - Both the per-coupon rate and choices work on every coupon rail. The in-Stripe app drawer doesn't offer choice coupons yet; mint those from the dashboard or let affiliates self-mint. ## See also - [Sub-affiliate recruiting](/docs/brands/sub-affiliate-recruiting): the other affiliate-network feature, a 2-tier program with a bounded override on top of normal commission, one recruiting level only. Works alongside coupon attribution; override fires whether the parent sale was attributed via click or coupon. - [Per-product commission rates](/docs/brands/product-commissions): set a different commission for specific products, on top of coupon attribution. --- # Pay per click or lead (CPC & CPL) Source: https://rekomi.com/docs/brands/cpc-and-cpl > When to use cost-per-click and cost-per-lead, how to set each up, the affiliate experience, and how Rekomi keeps them fraud-resistant. --- title: Pay per click or lead (CPC & CPL) description: When to use cost-per-click and cost-per-lead, how to set each up, the affiliate experience, and how Rekomi keeps them fraud-resistant. --- Most Rekomi programs pay on a **sale** (the Per sale model, one-time or recurring). Two legs pay earlier in the funnel: **Clicks** (pay per click, also called CPC) and **Leads** (pay per qualified lead, also called CPL). This guide covers when to reach for each and how to set them up. > **Want sale commissions AND a per-lead or per-click reward on one campaign?** You don't need a lead-only or click-only campaign for that. Under **What do you pay affiliates for?** tick more than one option (Sales, Leads, Clicks, in any combination) and set a rate for each; see [Combine sales, leads and clicks](/docs/brands/commission-models#combine-sales-leads-and-clicks-on-one-campaign). The CPC and CPL guides below describe campaigns that pay per click or per lead *only*, and everything in them (links, forms, fraud checks, budgets) applies the same way when those legs sit on a sale campaign. > **Paying per click and per lead is live.** Tick **Clicks** or **Leads** when creating a campaign (Starter plan and above); clicks and qualified leads are fraud-screened and awarded automatically as described below. > **Every click and lead is fraud-scored before it can be paid.** Leads are scored at the moment of submission; clicks are scored by a background job within about a minute of being recorded, always before the hourly awarding pass. Rekomi checks the real visitor's IP against IP reputation data (proxy, VPN, Tor, datacenter, and bot signals, plus a blended fraud score and recent-abuse history), holds anything high-risk out of payment for your review, and only ever pays traffic that clears. This up-front validation is the layer most affiliate tools skip, and it is what makes paying per click or lead safe. See [Keeping CPC and CPL honest](#keeping-click-and-lead-payouts-honest-cpc-and-cpl) below. ## Which one to use | You want to reward... | Use | Pays on | |---|---|---| | A sale or recurring revenue | **Sales** (CPS) | a paid invoice | | A qualified lead (trial, demo, waitlist) | **Leads** (CPL) | a form submission | | Raw traffic to your site | **Clicks** (CPC) | a validated click | Paying per click is rare for SaaS (clicks don't guarantee value) and best for paid content or syndication deals. Paying per lead fits products with a free trial or demo, where a signup is worth a known amount to you. Both require a **Starter plan or above**. > **Paying per click is invite-only, and so is any campaign that pays nothing per sale.** There is no public sign-up page for these campaigns and they are not listed in the creator network. You **invite each partner** and set their rate (per partner, or via a partner group), so paying per click or lead stays bounded by *who you approve* plus an optional campaign **budget cap**, not an open bounty anyone can claim. Auto-approve and public discoverability are turned off automatically. (A sale campaign that adds a per-lead reward may stay public.) ## Set up a pay-per-click campaign (CPC) 1. Create or edit a campaign and tick **Clicks** under **What do you pay affiliates for?** (in the API, `defaultCommissionModel` = `Cpc`; the rate is always a flat amount, a click has nothing to take a percentage of). Set the amount per validated click, for example `$0.50`. 2. Affiliates share their neutral tracking link, `rekomi.link/{their-slug}?p={campaign-id}` (Rekomi generates it on their campaign page). A visitor who clicks it is counted on Rekomi's edge, then forwarded to your campaign's destination URL. Make sure the campaign's Website / link to promote is set; it is the page visitors land on after the click is counted, and the link will not resolve without it. 3. Clicks are scored for IP fraud (proxy, VPN, datacenter, bot) by a background job within about a minute of being recorded, de-duplicated per device, capped per IP, and aggregated into commissions hourly. Anything high-risk is held for review, never paid automatically. Affiliates see their click count and earnings on their campaign page. Because the click is measured on Rekomi's side (not your server), neither you nor the affiliate can quietly change the number, which is what makes per-click payouts trustworthy. ## Set up a pay-per-lead campaign (CPL) 1. Create or edit a campaign and tick **Leads** under **What do you pay affiliates for?** (in the API, `defaultCommissionModel` = `Cpl`; always a flat amount). Set the amount per qualified lead, for example `$10`. 2. Affiliates share a Rekomi-hosted lead form, `/lead/{their-slug}`. A prospect who fills it in becomes a lead attributed to that affiliate. 3. Every submission must pass a Cloudflare Turnstile challenge, email-format and disposable-domain checks, and a self-lead guard before it earns. Duplicate submissions from the same person (including plus-aliases) are counted once. A lead earns the moment a valid form is submitted, you do not wait for a sale. If a trial later converts, tick **Sales** on the same campaign (below) instead of running a second one. ## Combine with sales, leads or clicks A click or lead campaign can pay for more than one thing. In the campaign form, and in Campaign settings under **Commission**, **What do you pay affiliates for?** is a multi-select: tick **Sales** to add a sale commission (percent or flat, with the usual commission length: first payment only, a set number of months, or the customer's lifetime), tick **Leads** on a click campaign to add a per-lead amount, or tick **Clicks** on a lead campaign to add a per-click amount. Partners see every leg on their invite and their campaign page ("$0.50 per click plus 10% per sale"). Being upfront about the boundaries: - A sale commission on a click or lead campaign pays on attributed sales and follows the campaign's commission length; **refunds claw it back** the standard way. - It sits **outside the campaign budget cap**: the budget keeps capping click and lead spend only. - Partner-specific rates (invites, partner groups, tiers) override the campaign's main rate only: the per-click amount on a click campaign, the per-lead amount on a lead campaign. The added legs pay the same amount to every partner. - A campaign that pays per click stays invite-only whatever else it pays; a lead campaign that also pays per sale may be opened to public sign-up. See [Commission models](/docs/brands/commission-models#combine-sales-leads-and-clicks-on-one-campaign) for the full rules. ## Rates, groups, budgets, and links - **Invite partners and set their rate.** From the campaign, invite a partner and set their `$ per click` / `$ per lead`. The campaign's default rate is just a fallback; a partner's own rate always wins. - **Partner groups (rate tiers).** On the **Commission** tab, create groups (for example "Standard $0.30/click", "Premium $0.50/click") and assign partners to one from the partner's detail page. Rate precedence is **per-partner override → group → performance tier → campaign default** (performance tiers mostly matter for sale models; only fixed-amount tiers can apply to CPC and CPL). One group can be the default that new partners inherit. ![The campaign Commission tab, where the default rates, the click and lead budget, and partner groups are configured](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-tiers.png?v=5) - **Budget cap.** Set an optional **campaign budget** (any campaign that pays per click or per lead). Once the campaign's click and lead rewards reach it, new clicks and leads stop earning. Live spent / remaining shows on the Commission tab. Sale commissions always pay outside this cap. - **Multiple destination links.** Every campaign that pays per click or per sale supports extra product & page links (deep links) to specific products or pages on **your own site**, each tracked separately (a lead-only campaign shares one lead form instead). On campaigns that pay per click these route through the neutral `rekomi.link/{slug}` domain, validated to your campaign's domain so the link can never be pointed at an arbitrary site; on sale campaigns they are direct links to the destination with the affiliate's own code. ## Keeping click and lead payouts honest (CPC and CPL) Because these models pay without a purchase behind them, Rekomi validates every click and lead up front, before it can ever become a payout, rather than clawing money back later. This is the layer most affiliate tools skip, and it is why paying per click or lead on Rekomi is safe: - **IP fraud scoring before payment.** Every lead is scored against IP reputation at the moment of submission, and every click within about a minute of being recorded: proxy, VPN, Tor, and datacenter detection, known-bot signals, recent-abuse history, and a blended fraud score. Anything the score flags as high-risk is held out of payment. - **A real visitor IP that is hard to spoof.** Rekomi reads the true client IP at the edge in a way an affiliate cannot practically forge, which is what makes the next two controls reliable. - **Per-IP and per-affiliate limits, plus de-duplication.** A single device or IP cannot earn unlimited clicks; repeat clicks from the same visitor collapse to one, each IP is capped at 100 payable clicks per day, and each affiliate is capped at 500 payable clicks (and 500 paid leads) per day by default, so no one partner can run up the budget in a day. - **Invalid-traffic quarantine you can review.** Flagged clicks and leads are recorded and quarantined, never silently deleted. If something was a false positive, validate it from the Conversions page (Leads or Clicks tab) and it becomes payable; if a Pending item looks wrong, you can invalidate it. - **Affiliate reputation.** An affiliate whose traffic is consistently flagged as invalid is surfaced for review and can be auto-suspended, so a repeat offender is stopped, not just their individual clicks. - **Fail-closed by design.** If a click's IP cannot be scored (for example the fraud service is briefly unreachable), it is held and retried for up to 3 days, then dropped, never paid. A lead that cannot be scored is rejected outright and not recorded. Money never moves on unverified traffic. - **Budgets, invite-only, and a payout hold.** A campaign budget caps total spend, campaigns are invite-only, and click and lead earnings sit in the standard payout grace window before any money moves, giving you time to review. ### Choose how strict the filtering is Every campaign that pays per click or per lead has a **Fraud filtering** setting (campaign Settings, next to the budget) with three levels: - **Strict** (the default): VPNs and proxies are blocked, and the tightest risk thresholds apply. Right for most campaigns. - **Standard**: VPNs and proxies are still blocked, with a little more tolerance on the blended risk score before a click or lead is held. - **Relaxed**: visitors on VPNs and privacy tools are allowed. Pick this when your audience legitimately uses them, for example privacy-focused or developer products. Whatever level you choose, the floor never moves: bots, Tor, and known-abuse traffic are always blocked, unverifiable traffic is always held rather than paid, and the per-IP and per-affiliate limits stay on. The levels only tune how much benefit of the doubt borderline traffic gets. ### See what was filtered The Fraud section of each campaign's **Performance** tab opens with a **Filtered, not billed** summary: how many clicks were blocked (and why), how many are currently held for checks, how many leads were quarantined, and roughly what that traffic would have cost you at your campaign's rates. Held clicks finish their checks automatically; you do not need to do anything with them. ![The campaign Performance tab's Fraud section where flagged clicks and leads are held for review](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaign-fraud-tab.png?v=6) There are two places you review this. Each campaign's **Performance** tab has a **Fraud** section that lists the clicks and leads Rekomi flagged and did not pay (high-risk IPs, bots, self-leads, disposable emails, and abusive affiliates); from there you can dismiss a false positive, mark it resolved, or ban a partner, which stops their future clicks and leads from earning immediately. Separately, a click or lead that was quarantined as invalid traffic shows an **Invalidated** status on the **Conversions** page (Leads or Clicks tab), where you can validate it to release it for payment, or invalidate a Pending item you judge fraudulent. CPC and CPL commissions are not refundable the way sales are, which is exactly why the checks happen before payment, not after. --- # Creative assets Source: https://rekomi.com/docs/brands/creative-assets > Give affiliates ready-to-use banners and ready-made posts (swipe copy) for a campaign. --- title: Creative assets description: Give affiliates ready-to-use banners and ready-made posts (swipe copy) for a campaign. --- Creative assets let you hand approved affiliates ready-made marketing material instead of leaving them to design their own. Upload banners and ready-made posts (swipe copy) once, per campaign, and every approved affiliate can grab them. ## Uploading Assets are managed in the campaign's Promo materials tab: - **Banners:** PNG, JPG, or WebP, up to 10 MB each. - **Ready-made posts:** text snippets, up to 10,000 characters each. Upload as many as you like; there's no limit beyond file size and character count. ## What affiliates see Approved affiliates see a "Creatives" section on their campaign page: - Banners show as previews with a copy-URL button and a download button. - Ready-made posts show with a one-click copy button. Assets only appear to affiliates who are approved on the campaign. ## Banner URLs Each banner gets a permanent public CDN link. It's safe to embed the URL directly anywhere (a blog post, an email, a landing page): treat it as public marketing material, not something that needs to stay private. ## Editing assets You can edit the name or text of an asset at any time. To swap the file behind a banner, delete the existing banner and upload a new one; there's no in-place file replace. ## See also - [Per-product commission rates](/docs/brands/product-commissions): another campaign-level tool affiliates rely on when promoting specific products. --- # Booking creators with deals Source: https://rekomi.com/docs/brands/creator-deals > Pay a creator upfront with escrow, optionally with a commission tail, end to end. --- title: Booking creators with deals description: Pay a creator upfront with escrow, optionally with a commission tail, end to end. --- A deal books a creator for a flat amount (a dedicated video, a newsletter feature, anything on their page) with the money held in escrow by Rekomi until you confirm the work. Optionally, a commission tail keeps paying them a percentage of tracked sales afterward, so the creator stays invested after the flat fee. Deals live under **Brand deals** in the sidebar, at `/dashboard/deals` (the same name the creator side uses for this rail). Rekomi takes no cut from the creator; you pay a capped service fee on the money moved, itemized before you pay: - **Deals you source yourself** (offering by email, accepting a creator's proposal link or email, their work-with-me form): your plan's rate (3%), minimum $5, **capped at $150 per deal**. A $20,000 deal costs you $150. - **Creators you discover in the Rekomi directory**: your **first** funded deal with that creator carries a one-time 10% match fee, capped at $500. Every deal with them after that, forever, is the capped 3% above. The fee preview and invoice say which one applies before you pay. The full schedule, with worked examples, is on the [brand deals fee sheet](https://rekomi.com/fees/deals). ## Finding creators `/dashboard/creators` is the directory of vetted creators with public pages: search by name, niche, or channel, view their page, and hit **Offer a deal** to start with their identity pre-filled. You can also offer a deal by email to any creator, on Rekomi or not: an address with no Rekomi account gets the deal terms by email with an invite to create a free creator account, and once they sign up with that address the deal is waiting for them to accept, decline, or message you (the deal card shows "Invited to Rekomi" until then). Or accept a **proposal** a creator sends you, as a link or an email. A proposal page shows the creator's pitch, their terms, and a printable invoice with its own number for your books; accepting takes you straight to funding the escrow, and the invoice link keeps working afterwards with its status updated as the deal is paid and released. When a creator sends a proposal to the email on your Rekomi account, it also rings your notification bell and appears under **Invites for you** at the top of `/dashboard/deals`, so you never have to dig it out of your inbox. The proposal page tells you which brand the deal will be added to; if you run several brands, switch to the right one in the dashboard before accepting. It also shows the Rekomi platform fee up front (standard 3% of the deal amount, capped at $150, added when you fund the escrow) and the escrow rule: your payment is held by Rekomi and released to the creator only after you mark the work complete. Accepting a deal never requires a subscription. A brand that only wants to work with a creator gets **deals-only access**: create a free brand account, accept the proposal, fund the escrow, and manage the deal and its messages from `/dashboard/deals`, all without picking a plan or starting a trial. A plan is only needed to run your own affiliate program (campaigns, tracking, and payouts to your affiliates); the deals page links to the plan picker whenever you want to add that. ![The Find creators directory of vetted creators with public pages](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/creator-directory.png?v=3) ## The flow 1. **Offer.** Pick the campaign that will host the tracking, what you are booking, the flat amount ($50 to $50,000 USD equivalent, quotable in any of 135+ supported currencies), and the tail (a recurring percentage is the default; one-time or none also work). The creator gets 14 days to accept before the offer expires. Tick **Split into milestones** to pay in parts instead of one lump (see [Milestones](#milestones) below). 2. **Fund.** Once accepted, fund the escrow: amount plus the service fee. Your first deal, verified or not, is funded from your bank-transfer balance: send a transfer to the details under Settings, Billing, Funding (it lands in one to three business days), then press Fund and the escrow is secured at once. Until your account is verified every deal funds this way; verified brands can charge their linked US bank by ACH from the deal after their first funding, which settles in about four business days. Shopify-billed brands: the fee goes to your Shopify invoice instead, and the charge is the amount only. A deal quoted in another currency converts at the market rate when you fund; the exact USD total and the locked rate are itemized on the invoice. While a charge settles the deal shows "settling", and both sides are notified when the money is secured. Funding requires the org owner in an interactive session, same as payout runs. 3. **Delivery.** The creator does the work and marks it delivered (you can also mark it yourself once the content is live). Marking delivered requires the escrow to be settled. 4. **Review and release.** Look at the work. Happy? Release: the creator is paid the deal amount through their Stripe payout account (Express or bank payout), minus only standard payout processing fees. A creator set up for PayPal only is asked to add a Stripe payout account before the release goes out. 5. **Auto-release.** If you take no action, the payment releases automatically **at the end of the review window** (7 days after delivery unless the deal terms set 3 to 14 days); we remind you by email at day 3 and tell you the exact date everywhere. Review on time. ![The Brand deals page: an offer waiting, a funded deal, and a delivered deal ready to release](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/creator-deals.png?v=4) ## Reviews, holding periods, and verification Deals move real money to people, so new accounts get the same care as payout runs: - **Verification before funding.** While the deals rail is new, funding a deal needs a verified account. You can create, negotiate and accept deals before that, and the fund step shows the verification card until a person at Rekomi has signed off. The two checks are described below under Get verified. - **A quick look before the first deal.** The first time a new account funds a deal, verified or not, a person at Rekomi looks at the account first. Nothing is charged while that happens; the deal shows "Under review" and usually clears within one business day. You get an email either way, and if we need something from you it says what. - **A holding period after release.** Until your account is verified, a released payment waits 7 days before it goes to the creator (both sides see the exact date on the deal). A creator whose payout account is under a week old waits the same 7 days, whoever the brand is. If anything looks wrong in that window, Rekomi can cancel the payment and refund your escrow. Verified brands release instantly. - **A cap until verified.** Funded deals and payout runs share one limit: $2,500 per rolling 30 days until you are verified. - **Get verified.** Open Settings, Billing, Funding. A deal-only account has two checks: who you are (verify your ID, or a bank account in your business's name) and how you pay (your first bank transfer landing counts); a website is optional. Submit, and a person signs off, usually within one business day. Verified brands can link a bank debit (used from the deal after their first funding), release instantly, and have no cap. - **The creator's side.** A release only goes through once the creator's tax form (W-9 or W-8) is on file and their payout account is finished. Deals pay out through Stripe (Express or bank payout); a creator set up for PayPal only is asked to add a Stripe payout account first. If anything is missing, the deal thread says so and the money waits; nothing is lost. ## Using Rekomi for deals only You do not need a plan to work with creators. A brand account that starts from a creator's proposal link (or only ever uses Brand deals) can run as many deals as it likes: accept or counter proposals, fund escrow, review and release milestones, message creators, invite teammates, and export deals for finance (Admins and Owners). The platform fee is collected on each deal. Your dashboard lands on Brand deals, and the affiliate-program pages sit behind one "Start an affiliate program" link. When you do want creators and affiliates earning commissions on tracked sales, start a plan from that link; it is the same account, so every deal, creator relationship, teammate, and invoice carries over, and the affiliate program pages simply unfold in the navigation. A brand that already runs an affiliate program sees Brand deals in the same navigation, with a count of deals waiting on you. ## Negotiating before acceptance Nothing is locked until one side accepts, so an offer can change shape without starting over: - **Edit your offer.** While a deal is Offered, **Edit offer** on its card lets you change the title, brief, amount, milestone schedule (for example split one payment into one per video), commission tail, and terms. The creator is notified and sees the new version (a creator who has not joined Rekomi yet gets the updated terms by email for the first three revisions). - **Counter-proposals.** The creator can answer your offer with different terms instead of a plain yes or no. The card then says the creator proposed different terms, and the deal thread shows what changed; **Accept their terms** locks the deal, **Revise** sends your version back, or cancel it. The same applies to a proposal a creator sent you: **Propose changes** on the proposal (in Invites for you, or from the deal page) turns it into an offer with your terms that the creator accepts, counters, or declines. That uses up the proposal link. - Each version is a numbered revision; if the terms change while you are looking at an older version, the accept button asks you to review the latest one first. Up to 20 revisions per deal; after that, settle the details in the thread. Cancelling the whole deal is only possible before funding (while Offered or Accepted). Once any milestone is funded, Cancel closes only the milestones nobody has funded yet; for the money already in escrow, the path forward is delivery and release, or a dispute. ## Milestones A deal can be paid in milestones: a deposit that reserves the slot, the rest on delivery, or one payment per video in a series. Either side sets the schedule up front (you on the offer form, or the creator on a proposal with its own milestone rows), and every milestone has a title, an amount, an optional description and target date. - **Each milestone is its own escrow.** Fund milestone 1 to start; fund the next when you are ready, or **Fund all remaining** in one click (that is still one bank charge per milestone, so each one can be released, or disputed, on its own). - **Each milestone is released on its own.** The creator submits a milestone when its work is delivered; you approve and release it, or ask for changes. The auto-release at the end of the review window and the day-3 reminder run per milestone, from the day it was submitted. - **Request changes** pauses the auto-release clock until the creator resubmits, so a milestone you are still reviewing can never release on its own. Up to five rounds per milestone; after that, release it or bring us in. - **One fee for the whole deal.** The platform fee is still 3% capped at $150 per deal (10% capped at $500 on a first directory deal); each milestone's charge carries its share until the cap is reached, so a $6,000 deal split three ways costs $60, $60, then $30. - **Cancelling after a milestone is funded** closes only the milestones nobody has funded yet; funded ones continue as agreed. - **Invoices.** On deals that started from a creator's proposal, each milestone has its own invoice under the proposal number (the number with -1, -2, ... appended; links are on the proposal page), and the bare invoice link is the deal statement with every milestone listed. A classic one-amount deal is simply a deal with one milestone, and works exactly as before. ## Terms, review window, and bookkeeping Every offer (and every creator proposal) can carry terms: a statement of work, usage rights and licence period, exclusivity, and the **review window**, the number of days you have to review each submitted milestone before it releases automatically (3 to 14 days, default 7). Accepting the deal records both sides' agreement with a timestamp, and the terms show on the deal card and the invoice. On the deal card you can also add a **PO number** and **cost center**; both appear on invoices and in the CSV export. A deal can also carry a **structured brief**: a one-liner, the full description of what you want made, the call to action, your promo link, up to 10 asset links, and the accounts you want tagged or mentioned. Both sides see the same brief on the deal page, and its links go through the same safety scan as everything else on Rekomi. You can edit it while the deal is Offered or Accepted with nothing funded; the moment the first milestone is funded it freezes, so what was agreed is what stays agreed (use the deal thread for notes after that). ## Proof of delivery When a creator submits a milestone they attach links to the delivered content and a short note, and confirm the sponsorship disclosure is in place. Everything shows on the milestone row so you can review without leaving the page. Under **Deal settings** an Owner can make the disclosure confirmation mandatory for every submission. Submitted milestones can also carry **results**. When a proof link is a video on the creator's own verified YouTube channel, Rekomi pulls the video's public views, likes, and comments automatically and refreshes them daily for 30 days; those numbers show as "verified via YouTube". Anything the creator types by hand is always labeled "creator-reported, unverified", so you can tell at a glance which numbers were actually checked. ## If the work is not right Say so in the deal's message thread first; most issues resolve as a revision, and on a milestone you can click **Request changes** to say what is missing while the clock is paused. If that does not settle it, open a **dispute** on the milestone: 1. Pick a reason (not delivered, quality, scope, late, payment, other), describe the problem, add evidence links, and propose an outcome: release the full amount to the creator, split it (part to the creator, the rest refunded to you), or refund it all. 2. The payment stays in escrow while the dispute is open: no manual or automatic release can touch that milestone. 3. The creator has 7 days to accept your proposal (applied immediately) or disagree. Disagreement, or silence past the deadline, escalates it to Rekomi, who reviews both sides and applies a decision. You can withdraw a dispute you opened while it is still waiting on the creator; once it has escalated, Rekomi resolves it. 4. Refunds go back to the funding bank charge; a partial split pays the creator their share through the normal payout rail. The platform fee collected at funding is not refunded (it covers escrow and the dispute process). Creators can open disputes too (for example when a brand goes quiet after delivery); you get the same response window. ## Changing the schedule Either side can **propose a change**: amend an unfunded milestone's title, amount, or date, or add a milestone. Nothing changes until the other side accepts; funded milestones can never be amended because their money has already moved. Up to five proposals can be open at once. ## Team controls, bulk offers, and exports - **Deal settings** (Owners only, top of the deals page): an **approval threshold** so deals at or above a total must be funded by an Owner (Admins can offer and review but see "approval required" at funding); the mandatory **disclosure confirmation**; and a **deal budget per campaign**, a hard cap on escrow funded through that campaign across all its deals. - **Send to several creators** on the offer form turns one brief into separate offers for up to 20 creator emails, each with the same amount, milestones, tail, and terms. - **Export CSV** downloads one row per milestone (amounts, fees, refunds, released amounts, timestamps, charge and payout ids, PO and cost center) for your finance system. - **Webhooks**: `deal.funded`, `milestone.submitted`, `milestone.released`, and `dispute.opened` are available under [Webhooks](/docs/developers/webhooks); the MCP server exposes `list_deals`, `get_deal`, `approve_milestone`, and `request_milestone_changes` (funding and releasing stay in the dashboard on purpose). ## Messages Every deal has a conversation thread at `/dashboard/messages` (or the Messages button on the deal card): live chat with typing indicators, unread counts, and the deal's history inline ("Funds are in escrow", "Payment released"). You can attach images and PDFs straight in the thread (up to 5 per message, 10MB each). Notification emails deep-link to the exact thread and stay quiet while you are actively viewing it. ![A deal conversation with escrow events inline](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/creator-deal-messages.png?v=3) ## Money details - The service fee snapshot is taken when you fund, so a plan change never reprices a live deal. - The one-time match fee applies only when the deal started from the directory **and** you have never funded a deal with that creator before; declined or expired offers never consume it. Once any deal with a creator is funded, every future deal together is the capped base fee, whichever side starts it. - Escrow is held by Rekomi and the release draws from your specific funding charge; your money is never pooled into someone else's payout. - A failed ACH charge steps the deal back to Accepted with an email to you; fund again to retry. - A failed release (creator payout account issue) keeps the money in escrow; retry once they fix their payout setup. - The commission tail lives in the campaign you picked: accepting the deal creates the creator's tracking link there, and tail commissions flow through the normal [payouts](/docs/brands/payouts). --- # Referrals Source: https://rekomi.com/docs/brands/customers > People your affiliates referred, leads and paying customers, grouped per person. --- title: Referrals description: People your affiliates referred, leads and paying customers, grouped per person. --- The Referrals page at `/dashboard/customers` shows everyone your affiliates referred: leads who haven't paid yet and paying customers alike. Where the [Conversions page](/docs/brands/sales) is a log of events, Referrals is a list of people: each row is one person, with everything they've done rolled up. ![The Referrals page with a table of referred people, their status, referring affiliate, payments, and lifetime value](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/customers/customers-list.png?v=3) ## What you see One row per person, with these columns: - **Person**: name and email as captured from your payment processor or lead tracking, with an avatar (their Gravatar when they have one, initials otherwise). - **Status**: **Lead** (referred but never paid) or **Customer** (at least one payment). - **Referred by**: the affiliate credited with the person's first conversion, linking to their profile. - **First seen**: when their first referred activity was recorded. - **Payments**: how many payments they've made. Refunded and denied sales don't count. - **Lifetime value**: what the person actually paid, net of refunds. - **Commission**: what their referrals earned the affiliate, including per-lead fees on campaigns that pay per lead (CPL). Click a row to open the person's detail page with their full ledger. All amounts are converted to your home currency. ## Finding people - **All / Leads / Paying** tabs split the list by status. - **Search** matches the start of a name or email. - The **campaign filter** narrows the list to people referred through one campaign. ## How people are grouped Rekomi builds this list from your referred sales and leads, deduplicating per person: rows sharing the same payment-processor customer id (for Stripe, the Stripe customer id) are one person, and rows without one group by their captured email. Two honest caveats: - If the same person shows up once under a processor customer id and separately under only an email, they appear as two rows; there is no cross-key merge. - Referred activity that captured neither a customer id nor an email can't be tied to a person, so it doesn't appear here (it still appears on the [Conversions page](/docs/brands/sales)). Synthetic test sales from the [Test Affiliate](/docs/brands/test-mode) never appear on this page. ## A note on privacy This page shows your referred customers' names and emails to everyone on your team. That's the point (it answers "who did this affiliate actually bring us"), but it means customer contact details are visible to every teammate with dashboard access. Keep that in mind when granting [team access](/docs/brands/team), and cover this data in your own privacy policy. --- # Your dashboard Source: https://rekomi.com/docs/brands/dashboard > Signing in lands on your campaigns, with every action a labeled button; analytics live under Reports. --- title: Your dashboard description: Signing in lands on your campaigns, with every action a labeled button; analytics live under Reports. --- Signing in lands you on **Campaigns** at `/dashboard`: the thing you actually work on, one click from everything. Each campaign is a row with its logo, name, a stats line (rate, affiliates, sales in the last 30 days, plus leads and paid clicks in the last 30 days when the campaign pays for those, unpaid commission), and a row of labeled actions, so nothing is hidden behind a click-and-hope table row. Your numbers live one click away under [Reports](/docs/brands/reports). ![The campaigns landing with a stats line and labeled action buttons on each campaign](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/campaigns-list.png?v=9) ## The campaign actions Every campaign row carries the same buttons: - **Manage campaign**: opens the campaign's detail page (overview, affiliates, performance, commission, coupons, promo materials, emails, settings). - **View signup page**: opens the public page affiliates use to join, in a new tab. - **Edit signup page**: opens the signup page editor. - **Performance**: jumps straight to the campaign's performance tab. - **Duplicate**: creates a new campaign prefilled with this one's settings. - **The status pill** (Live or Paused) is a dropdown: pause or resume the campaign right from the list. Archiving lives at the bottom of the campaign's Campaign settings tab. A layout switcher in the header flips between rows and a card grid; your choice is remembered. The **New campaign** button is always top-right. ## Your suggested next step Above the list, the dashboard shows one suggestion at a time, based on where your account actually is: 1. No campaigns yet: **Create your first campaign**. 2. Campaigns but no approved affiliates: **Invite your first affiliates**. 3. Affiliates but no clicks: **Install the tracking snippet**, then run a test sale. 4. Clicks but no sales: **Turn clicks into sales** with [coupons](/docs/brands/coupons). Each card is a shortcut, not a gate; you can do these steps in any order, and you can dismiss any suggestion for good. Once sales are flowing, the card disappears. ## Where the numbers live [Reports](/docs/brands/reports) (sidebar, or G then R) is the one analytics surface: KPIs, the trend chart, top affiliates, traffic sources, and CSV export, all scoped by a shared date range and campaign filter, with deeper reports on its pill nav. The [Conversions page](/docs/brands/sales) is the raw event log; Reports is the rollup. --- # Emails Source: https://rekomi.com/docs/brands/emails > The automated email catalog on each campaign, campaign-scoped switches, previews, and where broadcasts live. --- title: Emails description: The automated email catalog on each campaign, campaign-scoped switches, previews, and where broadcasts live. --- Emails live on each campaign: open a campaign and pick the **Emails** tab for everything your affiliates hear from you. Two sections: **Automated** is the catalog of every lifecycle email Rekomi sends on your behalf, and **Broadcasts** is where you write one-off sends. (The old `/dashboard/emails` page redirects here.) ## Automated emails Every automated email gets one row: its name and a short description, when it sends, how many went out in the last 30 days (with an open rate once something has sent), and an on/off switch. ![The Automated tab with a table of lifecycle emails, 30-day send counts, and on/off switches](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/emails/automated-catalog.png?v=4) The catalog: | Email | When it sends | Default | |---|---|---| | Application received | When someone applies | On | | Welcome / approved | When you approve an affiliate | On | | Application declined | When you decline an application | On | | Commission earned | When a commission is earned (a sale, a paid lead or a paid click) | Off until you turn it on | | Payout emails | When a payout starts and when it arrives | On | | Payout setup reminders | 3 and 14 days after joining without a payout method | On | | Claim reminders | When you nudge an imported affiliate to claim their account | On | | Invitations | When you invite an affiliate | On | | Getting-started tips | 3, 7, 14 and 30 days after the affiliate record is created | Off until you turn it on | | Tax form expiry | When a PayPal or bank-deposit affiliate's W-8 nears expiry | Always on | | New message notifications | When you send an affiliate a message | Always on | | Deal updates | When a deal is offered, funded, delivered or released | Always on | ### Always-on rows A few rows show a lock instead of a switch. These keep money and accounts working, so they cannot be turned off: - **Tax form expiry**: Rekomi holds payouts until a current W-8 is on file rather than applying US withholding, so the reminder always goes out. - **New message notifications** and **Deal updates**: these carry conversations and deal money the affiliate is a party to. ### Off by default **Commission earned** and the **Getting-started tips** series start off. They are the chattiest rows in the catalog, so you opt in rather than opt out. Everything else is on from day one. ## Campaign scope The switches you see are that campaign's effective settings. A campaign follows your account-wide defaults until you flip a switch on its Emails tab; from then on that campaign keeps its own setting, and your other campaigns are unaffected. Rows that say **Applies to all campaigns** (payout and tax emails) are account-wide by nature, so flipping them changes every campaign. ## Previews **Send me a preview** in the row menu sends a sample of that email, with your real branding, to your own verified inbox. There is deliberately no recipient field, so a preview can never mail anyone else. A few rows have no generic sample because you write their content per send (claim reminders, invitations, deal updates); those tell you there's no preview. ## The getting-started tips series The Getting-started tips row is a short drip of four emails that helps a new affiliate get moving. How it behaves: - It sends 3, 7, 14 and 30 days after the affiliate record is created. - It skips stages the affiliate has outgrown: the early nudges don't send once the affiliate already has clicks (day 3) or a sale (day 7). The later check-ins send with the affiliate's real numbers. - Turning the series off **skips** stages rather than delaying them. A stage that comes due while the switch is off is consumed; turning the series back on later never sends stale mail for stages that already passed. - It only applies to affiliates created after the feature went live. Affiliates who existed before that never enter the series. ## Broadcasts The Broadcasts section holds the composer for one-off sends to all (or filtered) affiliates, with recipient counts, plan limits, and unsubscribe handling. The composer opens with the campaign you came from pre-selected. See [Broadcasts](/docs/brands/broadcasts) for the full walkthrough. --- # Free month and store credit rewards Source: https://rekomi.com/docs/brands/free-month-rewards > Reward affiliates with credit instead of cash. A refer-a-friend campaign gives the referrer a free month of your product on your billing platform (Stripe, Chargebee, Recurly, Paddle, Braintree, Lemon Squeezy, Dodo Payments or Mollie); a store campaign on Shopify, Wix or BigCommerce gives store credit. --- title: Free month and store credit rewards description: Reward affiliates with credit instead of cash. A refer-a-friend campaign gives the referrer a free month of your product on your billing platform (Stripe, Chargebee, Recurly, Paddle, Braintree, Lemon Squeezy, Dodo Payments or Mollie); a store campaign on Shopify, Wix or BigCommerce gives store credit. --- A free month reward turns a campaign into a refer-a-friend program for your own customers. A customer shares their link, a friend becomes a paying customer, and the referrer gets a free month of your product on your own billing platform. The credit comes off their next invoice automatically. Nobody needs a payout method, a bank connection or a tax form. Free month rewards work on eight billing platforms: Stripe (through the [Rekomi app for Stripe](/docs/brands/install/stripe-app)), Chargebee, Recurly, Paddle, Braintree, Lemon Squeezy, Dodo Payments and Mollie. Each one carries the credit in its own way; the [Billing platforms](#billing-platforms) section below says exactly what happens on yours. Available on the Starter plan and above. ## How it works 1. You set a campaign's reward to **Free month on your product** instead of a cash commission. 2. Your customers join the campaign's signup page and share their referral link (or a coupon code). 3. When a referred friend pays their first invoice, Rekomi records the sale as usual and works out the referrer's credit. 4. Rekomi finds the referrer on your billing platform by email, sizes the credit from their own plan, and applies it there: a customer balance credit, a one-cycle discount, a free billing period or a skipped payment, depending on the platform. It comes off the referrer's next invoice. 5. The referrer gets an email that their free month landed. You see every credit on the Conversions page. One credit per new paying friend. Renewals of the friend's subscription never mint a second credit, and a friend who cancels and comes back later does not either. ## Setting it up On the campaign form, under **Commission**, keep **Sales** selected and choose the **Reward**: - **Cash commission** is a normal affiliate campaign. - **Free month on your product** is the refer-a-friend reward. A free month campaign pays for sales only; the Leads and Clicks cards switch off. The commission length is fixed at one credit per new paying friend. You then choose how the credit is sized: - **One month of their current plan** (the default). Rekomi reads the referrer's active subscription on your billing platform and credits one month of its list price. A yearly plan counts as one twelfth. Metered or tiered prices fall back to the referrer's last paid invoice. This is the only choice on Lemon Squeezy, which can give a free billing period but not an amount. - **What their friend paid on the first invoice.** The friend's first payment, before tax, in the friend's currency. - **A fixed amount.** The same credit for every referral, in your home currency. Two optional settings sit beside that choice. **Cap per referral** limits what one referral can credit, whatever plan the referrer is on. **Hold each credit for my approval** keeps every credit on the Conversions page until you approve it; off means it is applied the moment the friend's first invoice is paid. Braintree campaigns also have a **Braintree discount id** field, explained under Billing platforms. ## What you need - A connected billing platform that can carry the credit. On Stripe that is the Rekomi app for Stripe installed from the Stripe App Marketplace by your Stripe account owner (accepting the app's permissions in the Stripe dashboard is not enough on its own; the marketplace install completes the connection). On Chargebee, Recurly, Paddle, Braintree, Lemon Squeezy, Dodo Payments and Mollie it is the same key you connected in Integrations for sale tracking. The credit is applied on your own account there, so this connection is what posts it. - A way for the friend's payment to attribute to the referrer. The lightest path is the [Rekomi head script](/docs/brands/install/stripe) on your site with its email capture on, which records the friend's email at signup so their first invoice matches. A per-referrer coupon code works with no code at all. Developers can stamp `rekomi_affiliate_id` on the subscription instead. - If more than one billing platform is connected, the credit goes to the platform the friend paid on. A sale that does not say where it was paid waits as Needs attention so you can pick the customer, which also picks the platform. - The Starter plan or above. Trials count. ## Where the referrer's credit goes Rekomi looks for the referrer on your billing platform in this order: a customer already known from their own past purchase through Rekomi, then a customer whose email matches the email they joined the campaign with (or the email on their Rekomi account). When several customers match, the one with an active subscription wins, then the newest. Mollie has no email search, so Rekomi checks your newest 250 customers there; a referrer who bought through Rekomi before is always found. If no customer matches, the credit waits as **Needs attention** and Rekomi retries daily for a week. The referrer gets one email asking them to make sure their account with you uses the same email. On the sale's page you can **Pick customer** from the matches, paste a customer id, or cancel the credit. The credit is always applied in the referrer's own currency on your billing platform. A fixed amount in a different currency waits as Needs attention with the reason. ## Refunds A full refund of the friend's first payment reverses the credit. On Stripe that is a positive balance transaction on the referrer's customer (if they already used the credit, Stripe adds the difference to their next invoice). On Chargebee, Recurly and Dodo Payments the credit is deducted or voided while it is unused. On Paddle, Braintree, Lemon Squeezy and Mollie the discount, free period or skipped payment is removed while the next billing date is still ahead. A credit the referrer has already used on those platforms cannot be taken back automatically; the sale then reads Reversal failed and you decide whether to handle it yourself. Partial refunds leave the credit alone. A credit that had not posted yet is canceled instead. ## What you see - **Conversions** lists every sale on a free month campaign with a Free month pill and the credit's status: Pending, Awaiting approval, Applied, Needs attention, Canceled, Reversing, Reversed or Reversal failed. The sale's page shows the referrer's customer on your billing platform, the credit's reference there (balance transaction, credit invoice, discount or subscription), the reason when something needs you, and the actions: Approve, Retry now, Pick customer, Cancel credit. - **Campaign overview** shows how many free months the campaign has given and how many are waiting on you. - **Payouts** does not change. Credit rewards never enter a payout run, never count toward the 3% on payouts, and never appear on a 1099. - The **Free month rewards** email row under Settings, Emails covers the three notices to referrers: the credit landed, the account could not be matched by email, the credit was reversed. ## What the referrer sees The campaign page reads "You get one month free for every friend who becomes a paying customer", with Free months earned and Pending credits in place of the money tiles. Their conversions ledger shows each referral as a free month with its status. Someone whose every campaign rewards with a free month never sees payout setup, payout reminders or tax form prompts. ## What it never does - It never moves money through Rekomi. The credit is your own money on your own billing platform. - It never pays a cash commission on a free month campaign. Partner groups, performance tiers, per-affiliate rates, coupon splits, per-product rates and assigned partners do not apply there. Run a separate campaign for outside affiliates who should earn cash. - It never credits a self-referral. A friend whose email is the referrer's own, or whose customer on your billing platform is the referrer's own, records the sale with no credit. - It never posts a credit for a test row. The seeded Test Affiliate and test-mode sales record the sale and mark the credit as a test. ## Needs attention reasons | Reason | What it means | What to do | |---|---|---| | Customer not found | No customer on your billing platform matches the referrer's email | Pick the customer on the sale's page, or cancel the credit | | No active subscription | The referrer has no active subscription to size a month from (or, on Paddle, Braintree, Lemon Squeezy and Mollie, none to attach the credit to) | Switch the campaign to a fixed amount where the platform allows it, or cancel | | Currency mismatch | The referrer's customer is settled in a different currency than the credit | Use one month of their plan instead of a fixed amount, or cancel | | Stripe app required | Rekomi could not use your Stripe App connection | Reconnect the Rekomi app for Stripe from Integrations, then retry | | Billing platform required | No connected billing platform can carry the credit | Connect one of the eight platforms from Integrations, then retry | | Platform not clear | The sale did not name its billing platform and more than one is connected | Pick the customer on the sale's page, which also picks the platform | | Key refused | Your billing platform refused Rekomi's key | Reconnect it from Integrations, then retry | | Existing discount | The referrer's Paddle subscription already carries a discount, and Paddle allows one at a time | Remove it in Paddle and retry, or cancel | | Braintree discount needed | The campaign has no Braintree discount id yet | Create a discount in your Braintree Control Panel, enter its id on the campaign, then retry | | Not supported here | The campaign uses a fixed or friend-paid amount and the platform (Lemon Squeezy) can only give a free billing period | Switch the campaign to one month of their plan, then retry | | Amount too large | The credit is above the safety ceiling | Cap it on the campaign, then retry | Anything else is a temporary error from the billing platform. Rekomi keeps retrying with growing gaps for a week; you can also retry from the sale's page at any time. ## Store credit for Shopify, Wix and BigCommerce Many stores would rather reward affiliates and referring customers with store credit than cash. On Shopify, Wix and BigCommerce the campaign's Reward switch reads **Store credit on your store**, and it works like this: - **What the affiliate earns.** Either the campaign's commission rate on each referred order (a percentage or a flat amount, the default) or a fixed amount per sale. Every paid order earns, repeat customers included; this is a commission delivered as credit, not a one-time referral bonus. Partner groups, performance tiers, per-affiliate rates, coupon splits and per-product rates do not apply to credit campaigns. - **Where it goes.** On **Shopify** the amount is added to the affiliate's store credit account (Shopify's own feature, applied at checkout when the affiliate is signed in with the same email). On **BigCommerce** it is added to the affiliate's customer store credit balance, in your store's currency. On **Wix**, which has no customer credit balance, Rekomi issues a gift card for the amount and Wix emails it to the affiliate; the site needs the Wix Gift Card app installed. - **Matching the affiliate.** Rekomi finds the affiliate's customer account on your store by their Rekomi email. If there is none yet, the credit waits under Needs attention until you pick the customer or the affiliate creates an account with that email; nothing is lost. - **Refunds.** A refunded order takes the credit back: a debit on the Shopify or BigCommerce balance, a disabled gift card on Wix. If the affiliate already spent it, the credit shows as consumed and nothing more happens. - **Permissions.** Shopify needs the write store credit permission. Stores that installed the Rekomi app before this permission existed keep their old grant until the merchant reopens the app and approves the update; until then a credit waits under Needs attention with a "reopen the Rekomi app in Shopify" note. BigCommerce needs the Customers modify permission on the app; Wix needs the Gift Cards app on the site. - **No payout or tax paperwork.** Store credit never enters a payout run, never counts toward the 3% on payouts, and never appears on a 1099. Affiliates on a store-credit campaign need no payout method and no tax form. - **Approval and caps.** The same approval switch and per-sale cap as the free month apply. You turn it on under the campaign's Sales leg, Reward, from the web dashboard or from the embedded Rekomi app in your store admin; a campaign can switch back to cash at any time (credits already posted stay). ## Billing platforms Each platform carries the credit with what it offers. The referrer's experience is the same everywhere: their next invoice is smaller or free, and they get the same email. | Platform | How the free month is applied | Reversal on a full refund | |---|---|---| | Stripe | A negative balance transaction on the referrer's customer, through the Rekomi app for Stripe. Stripe takes it off the next invoice. | A positive balance transaction. If the credit was already used, Stripe adds the difference to the next invoice. | | Chargebee | Promotional credits added to the referrer's customer, applied to their next invoice. | The credits are deducted while unused. | | Recurly | A credit line item on the referrer's account, closed into a credit invoice that Recurly applies at the next renewal. | The credit invoice is voided while unused. | | Dodo Payments | A credit in the referrer's wallet, applied to their next invoice. | A matching debit while the credit is unused. | | Paddle | A one-time discount (a percentage for one month of their plan, or a flat amount) on the referrer's subscription from the next billing period, for one period only. Paddle allows one discount per subscription. | The discount is removed while the next billing date is still ahead. | | Braintree | Your Control Panel discount added to the referrer's subscription for one billing cycle, with the amount overridden to the credit. Braintree cannot create discounts by API, so you create one once (any amount) and enter its id on the campaign. | The discount is removed while the cycle has not billed. | | Lemon Squeezy | The referrer's subscription is paused in free mode until one billing period later, so the next charge is skipped. Only one month of their plan is possible here. | The subscription is unpaused while the free period is ahead. | | Mollie | For one month of a monthly plan, the subscription's next payment date moves one month forward, so one payment is skipped. For anything else (a fixed or friend-paid amount, or a plan that is not monthly), the next payment is reduced by the credit, never below 1.00, and Rekomi restores the amount the day after that payment runs. | The payment date is moved back, or the amount restored, while the next payment has not run. | Stores (Shopify, Wix, BigCommerce) and the gateways with no credit primitive (Polar, Creem, Gumroad) keep cash commissions, and the Free month option does not appear on their campaign forms. --- # Funding countries Source: https://rekomi.com/docs/brands/funding-countries > Which countries brands can fund payouts from, and which funding rails (ACH, SEPA, Bacs, PAD, bank transfer) each country gets. --- title: Funding countries description: Which countries brands can fund payouts from, and which funding rails (ACH, SEPA, Bacs, PAD, bank transfer) each country gets. --- This page answers one question: **where can your business be, and how do you fund payouts from there?** It is about you, the brand. Where your *affiliates* can be paid is a separate list of 130+ countries; see [Payout countries](/docs/affiliates/supported-countries) for that side. The short version: brands in virtually every country in the world can fund payouts. Four regions get an automatic bank debit, and everyone gets the universal bank-transfer rail. The only exclusions are internationally sanctioned countries, listed at the bottom. ## The five funding rails | | Rail | Requires | Fee | | --- | --- | --- | --- | | | US bank debit (ACH) | A US bank account | 3% platform fee, nothing else | | | EU bank debit (SEPA) | A euro-denominated account at a SEPA-zone bank | 3% + ~1% at-cost currency conversion | | | UK bank debit (Bacs) | A UK bank account | 3% + ~1% at-cost currency conversion | | | Canadian bank debit (PAD) | A Canadian bank account | 3% + ~1% at-cost currency conversion | | | Bank transfer | Any bank that can send USD (domestic transfer, SWIFT, or Wise) | 3% platform fee, nothing else | Full mechanics, timings, and receipts are on the [Payouts](/docs/brands/payouts#funding-your-payouts) page. What follows is the country-by-country availability. ## Every country, searchable Every country in the world, row by row, with the funding rails available to a brand banking there. Search by name or filter by rail; sanctioned countries are listed too, marked not supported. ## Which rails your country gets (summary) | Your business banks in | Automatic debit | Bank transfer | | --- | --- | --- | | **United States** | US bank debit (ACH) | Yes | | **Eurozone** (Austria, Belgium, Croatia, Cyprus, Estonia, Finland, France, Germany, Greece, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Portugal, Slovakia, Slovenia, Spain) | EU bank debit (SEPA) | Yes | | **United Kingdom** | UK bank debit (Bacs); SEPA also works if you hold a euro account | Yes | | **Canada** | Canadian bank debit (PAD) | Yes | | **Rest of the SEPA zone** (Bulgaria, Czech Republic, Denmark, Hungary, Poland, Romania, Sweden, Andorra, Gibraltar, Iceland, Liechtenstein, Monaco, Norway, San Marino, Switzerland, Vatican City) | EU bank debit (SEPA), using a euro-denominated account; many banks there offer one, and a Wise EUR account always works | Yes | | **Every other country** | None today; use bank transfer | Yes | Two practical notes: - **Bank transfer is available to every row above**, not just the last one. Plenty of US and EU brands choose it anyway: it is the cheapest rail (never a currency-conversion line), it has no mandate, it has no cap once your account is verified, and money is available the moment it arrives. - **A Wise account effectively unlocks the debit rails from anywhere.** Wise issues EUR accounts with SEPA details and USD accounts with US routing details, so a brand in, say, Türkiye or Singapore who prefers set-and-forget debits over transfers can route through Wise. Bank transfer is still the simpler path for most. ## Countries that cannot fund payouts A small set of countries cannot fund payouts, for two related reasons. Iran, North Korea, Syria, Cuba, and the Crimea, Donetsk, and Luhansk regions of Ukraine are under comprehensive US sanctions, so no transaction can be accepted from them. Russia and Belarus are blocked in practice as well: our payment processor does not serve them and most international banking channels to them are closed. Beyond that list, a few high-risk jurisdictions (for example Sudan, Myanmar, and Venezuela) are not sanctioned outright but can see individual transfers refused by intermediary banks or the processor. If you are in one of these and planning a program, write to support@rekomi.com first and we will confirm your corridor before you commit. Everyone else, from Argentina to Vietnam (and Ukraine itself, outside the occupied regions), can fund payouts today via bank transfer, with the same flat 3% platform fee a US brand pays. Sanctions lists change over time; this section reflects them as of August 2026. ## Related - [Payouts: funding your payouts](/docs/brands/payouts#funding-your-payouts): mechanics, timings, receipts, and reversals. - [Payout countries for affiliates](/docs/affiliates/supported-countries): every country your affiliates can be paid in, with the method and fee (Stripe Express, bank deposits, and PayPal, 130+ countries, searchable). --- # Integrations Source: https://rekomi.com/docs/brands/integrations > Connect Stripe, Paddle, Braintree, Shopify, WooCommerce, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, or Mollie for native sales tracking, run payouts through Stripe, and use Zapier for no-code automations across 8,000+ apps. --- title: Integrations description: Connect Stripe, Paddle, Braintree, Shopify, WooCommerce, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, or Mollie for native sales tracking, run payouts through Stripe, and use Zapier for no-code automations across 8,000+ apps. --- Rekomi connects to the rest of your stack in two ways. **Native billing connections** drive the money flow: incoming sales become attributed conversions. Stripe is the deepest, connected through the Rekomi app for Stripe; Paddle, Braintree, Shopify, WooCommerce, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connect natively for conversion tracking too, and any other billing platform attributes via the server-to-server API. **CRM connections** (39 platforms) move approved affiliates into the audience you already manage, and on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel the same connection also credits sales to the affiliate whose link brought the customer; see [CRM and email integrations](/docs/brands/crm). **Zapier** drives the operational flow: Slack pings, Mailchimp tags, HubSpot deal updates, Google Sheets rows, Typeform-driven approvals, anything you want to wire across the 8,000+ apps Zapier supports, with no code. ![The integrations hub with cards for the sending domain, API keys, webhooks, the AI assistant, and Zapier](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/integrations-hub.png?v=4) One rule applies across all of the native billing connections: **one billing source per workspace**. A workspace tracks sales from a single connected processor (or the S2S API) at a time, so connecting one blocks connecting another until you disconnect it. ## Zapier The Rekomi app on Zapier exposes 9 triggers, 9 actions, and 4 searches. Use it to notify your team when an affiliate signs up, sync conversions into your CRM, send urgent emails on failed payouts, auto-tag approved partners in your email tool, or approve vetted applicants the moment they submit a Typeform, all without writing code. Connection is one click: search for **Rekomi** in Zapier, click Connect, sign in at `app.rekomi.com/oauth/authorize`, pick your org, click Allow. Connections are per-Zapier-account, so each teammate connects their own Rekomi login and the audit trail stays clean. Requires the **Starter** plan or above. Full walkthrough, trigger / action / search catalog, six worked recipes, error handling, and the OAuth security model are in the [Zapier integration guide](/docs/developers/zapier). ## Stripe Stripe is the deepest native connection, made by authorizing the [Rekomi app for Stripe](/docs/brands/install/stripe-app). Sales and refunds come from your Stripe events automatically. (Paddle, Braintree, Shopify, WooCommerce, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connect natively too, covered below; any other gateway tracks via the S2S API.) We never hold your customer's money. This section explains the connection flow and the data model. The same app also puts the program inside your Stripe Dashboard: see referred customers, attribute sales, approve, adjust, void, or claw back commissions, issue credits, and run payouts without leaving Stripe. One authorization covers both the tracking connection and the in-Dashboard panels. ### Where to connect `/dashboard/integrations` shows your Stripe connection status. If you are not connected yet, click "Connect Stripe" and Rekomi sends you to Stripe's hosted consent screen for the Rekomi app. ### What Rekomi requests The app requests scoped permissions rather than blanket account access. In practice Rekomi uses them to: - Read your subscriptions, invoices, charges, and customers (to identify which customer was referred, how much they paid, and whether the charge was later refunded). - Write promotion codes (to mint affiliate coupon codes on your account). - Read your account information (to display your account ID and keep the connection healthy). ### What Rekomi stores After you approve the app, we store on your workspace's connection record: - Your connected account id (the `acct_...` identifier) - Account-scoped OAuth tokens, encrypted at rest - Connection-health timestamps We never store your raw API key. The tokens are scoped to the app's permissions and die when you uninstall it. ### Switching Stripe accounts If you connect a different Stripe account to an organization that already has one linked, Rekomi accepts the swap and drops the old connection, so events stop flowing from the previous account immediately. To also remove the app's grant on that old account, uninstall the Rekomi app from its Stripe Dashboard. The one hard stop: a Stripe account that is already linked to a *different* Rekomi organization is rejected as already linked, which prevents one workspace from silently claiming another's billing account. ### Webhooks Rekomi listens for Once connected, Rekomi processes these Stripe events to drive the platform: - `checkout.session.completed` (for billing on your Rekomi subscription) - `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted` (your customers' subscriptions) - `customer.subscription.trial_will_end` (for your own Rekomi trial) - `invoice.paid`, `invoice.payment_failed`, `invoice.upcoming` (drives sales attribution) - `charge.refunded` (reverses commission proportionally when a customer is refunded) - `customer.updated` (syncs billing email) - `payment_method.attached` (logged for audit) The webhook handler verifies signatures with the webhook signing secret and sets the org context before any writes, so multi-tenant isolation holds. ### Disconnecting To disconnect, uninstall the Rekomi app from your Stripe Dashboard (Settings, then Installed apps); `/dashboard/integrations` links there directly. Rekomi stops receiving new events, and sales already recorded stay on the affiliate ledgers. Re-installing the app restores the connection to the same workspace. ### Affiliate-side Stripe Connect Note that affiliates also use Stripe, but on the **affiliate** side and through a different mechanism: they connect their own Stripe Express account to receive payouts. That is a separate flow documented in [Affiliates: Stripe Connect](/docs/affiliates/stripe-connect). Your brand connection and an affiliate's payout account are independent. ## Paddle Brands on **Paddle Billing** connect natively, with no backend relay to build. You point a Paddle notification destination at a webhook URL unique to your workspace and paste its signing secret into Rekomi; Rekomi verifies every event and records the conversion. This is for tracking your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Paddle**. Rekomi shows your unique webhook URL and the exact events to enable. The step-by-step guide is [Install on Paddle](/docs/brands/install/paddle). ![The Connect Paddle page in Rekomi showing the per-workspace webhook URL, events to enable, and the signing secret field](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/gateways/connect-paddle.png?v=3) ### How it works - You carry the affiliate referral into Paddle Checkout via `customData.rekomi_ref` (the affiliate's link slug). - Paddle sends `transaction.completed`, `subscription.created` / `subscription.updated` / `subscription.canceled`, and `adjustment.created` / `adjustment.updated` to your Rekomi webhook URL. - Rekomi verifies the `Paddle-Signature` (HMAC-SHA256 over the timestamp-prefixed raw body) with your destination's signing secret, then records sales and reverses commission proportionally on refunds and chargebacks. ### What Rekomi stores The destination signing secret and the optional read-only API key are encrypted at rest and never returned by the API. Rekomi also keeps a per-workspace routing token (the id in your webhook URL) and connection-health timestamps. ### Security The webhook endpoint is public but every event must carry a valid HMAC signature over the exact request body, inside a short replay window; unknown tokens and bad signatures are rejected. The org is resolved from the routing token and the tenant context is set before any write, so multi-tenant isolation holds. The optional API key is read-only and used only for health checks and reconciliation, never on the event-recording path. ## Braintree Brands on **Braintree** (a PayPal company) connect natively for **subscriptions**, with no backend relay to build. You point a Braintree webhook at a URL unique to your workspace and paste read-only API credentials into Rekomi; Rekomi verifies every event and records the conversion. This tracks your recurring sales (so affiliates get credited) and is independent of how you pay for Rekomi. A scope note, stated plainly: Braintree fires webhooks only for subscriptions and disputes, not for one-time card sales or card refunds. So the native flow covers recurring revenue and automatic chargeback clawback; one-time sales use the [server-to-server API](/docs/developers/s2s-tracking), and one-time card refunds are handled by denying the commission manually. ### Where to connect Setup → Connect payment gateway → **Connect Braintree**. Rekomi shows your unique webhook URL and the exact notification kinds to enable. The step-by-step guide is [Install on Braintree](/docs/brands/install/braintree). ### How it works - You carry the affiliate referral onto each sale via a Braintree custom field named `rekomi_ref` (the affiliate's link slug), read back from the charge transaction. - Braintree sends `subscription_charged_successfully`, `subscription_canceled` / `subscription_expired`, and the `dispute_*` events to your Rekomi webhook URL. - Rekomi verifies the `bt_signature` (the Braintree SDK validates it offline against your stored keys), then records recurring sales and reverses commission proportionally when a dispute is lost. ### What Rekomi stores Your Braintree Merchant ID plus the Public and Private keys are encrypted at rest and never returned by the API (the Merchant ID is shown back as a non-secret identifier). Rekomi also keeps a per-workspace routing token (the id in your webhook URL) and connection-health timestamps. ### Security The webhook endpoint is public but every event is verified by the Braintree SDK against your stored keys before anything is trusted; unknown tokens and bad signatures are rejected. The org is resolved from the routing token and the tenant context is set before any write, so multi-tenant isolation holds. The recording path verifies offline (no Braintree API call), so a Braintree API outage cannot stop verified conversions from being recorded. Use a read-only Braintree user for the credentials. ## Shopify Brands on **Shopify** connect natively through the Rekomi app for Shopify, live on the Shopify App Store, with no theme edits and no relay to build. Install the app, approve the requested permissions, and pick the campaign you want orders attributed to; Rekomi captures the affiliate click and reads each order through Shopify's order webhook server-side. This tracks your store's sales (so affiliates get credited); your Rekomi subscription itself is billed through Shopify. Most Shopify affiliate apps charge a percentage of your referral sales. Rekomi charges 3% of the commissions you actually pay out, not on sales, and runs the payouts and taxes for you. ### Where to connect Install Rekomi Affiliate Marketing from the Shopify App Store. After install, the app opens on its Subscription page: pick a plan, approve the charge on Shopify's billing screen (every plan starts with a 14-day free trial), and you land back on that billing page with the trial running. The setup guide then walks you through linking your store to a campaign. The step-by-step guide is [Install on Shopify](/docs/brands/install/shopify). ### How it works - The app captures the affiliate click and stamps the referral onto the order, so no theme or checkout edits are required. - Shopify sends order and refund webhooks to Rekomi; verified orders record conversions and refunds reverse commission proportionally. - If you prefer to wire it up yourself, a manual Custom Pixel plus webhook-to-S2S fallback is documented in the install guide. ### What Rekomi stores The Shopify access token granted at install is encrypted at rest and never returned by the API. Rekomi also keeps your shop domain and connection-health timestamps. The app requests seven permissions: read-orders and read-customers power attribution and the top-customer invites, read-products powers the product catalog and picker, write-discounts exists only to mint the affiliate discount codes you create in the app, write-pixels plus read-customer-events power the app's web pixel, the sandboxed analytics worker Shopify runs for link click tracking (it reads only the landing URL's referral parameter and the checkout confirmation, never customer details), and write-store-credit exists only to post store credit rewards when a campaign uses them. Rekomi never edits your theme, storefront content, products, or orders; the pixel is installed through Shopify's own pixel system, not a theme change. ### Store credit rewards A Shopify campaign can reward affiliates with store credit instead of cash: the commission lands on the affiliate's Shopify store credit account and a refund takes it back. See [Store credit rewards](/docs/brands/free-month-rewards#store-credit-for-shopify-wix-and-bigcommerce). ### Shopify Collabs Shopify ships its own native affiliate program called Collabs. Rekomi adds cross-platform affiliate accounts (one affiliate working multiple brands), commission tiers, sub-affiliate recruiting, and the open creator network on top. You can run both side by side on the same store. ## WooCommerce Brands on **WooCommerce** connect natively through the Rekomi WordPress plugin, live on the WordPress plugin directory. Install, activate, and click Connect on the Rekomi page in wp-admin; the plugin then reports every order server-to-server, so attribution works on every payment method your store accepts (WooPayments, Stripe, PayPal, Square, cash on delivery, and the rest), on both the block checkout and the classic one. ### Where to connect Install the plugin from **Plugins > Add New** (search "Rekomi"), open **Rekomi** in the admin sidebar, and click **Connect to Rekomi**. Connecting creates your workspace automatically, and the full dashboard runs inside wp-admin. The step-by-step guide is [Install on WooCommerce](/docs/brands/install/woocommerce). ### How it works - Referral clicks are captured first-party in a cookie on your own domain; the plugin stamps the referral onto the WooCommerce order at checkout. - Order and refund events post server-to-server from your site to Rekomi: verified orders record conversions, and refunds claw back commission based on the refunded line items. - Affiliate coupon codes are minted as real WooCommerce coupons, so code redemptions attribute even without a click, and WooCommerce Subscriptions renewals accrue recurring commissions. ### What Rekomi stores The connection key issued at Connect is encrypted at rest, and deactivating the plugin revokes it server-side. Order data (id, totals, currency, line items, coupon codes, customer name/email) flows to Rekomi for attribution; Rekomi never edits your theme, products, or checkout. ## Wix Brands on **Wix** connect natively through the **Rekomi Affiliate Marketing** app, live in the Wix App Market, with nothing to paste and no relay to build. Install the app and setup is done: it injects the tracking script automatically and records each paid store order server-side. This tracks your store's sales (so affiliates get credited); your Rekomi subscription is billed through Wix. Most affiliate apps charge a percentage of your referral sales. Rekomi plans start at $29 a month with a 14-day free trial, plus 3% of the commissions you actually pay out, not a cut of sales, and Rekomi runs the payouts and taxes for you. ### Where to connect Install from the Wix App Market: search "Rekomi" or open the Rekomi Affiliate Marketing listing and click Add to Site. After install, everything lives in the Rekomi app inside your Wix dashboard: pick a plan on Subscription (the 14-day free trial is once per Wix account, billed through Wix), create your campaign, and manage affiliates, sales, and payouts from there. The step-by-step guide is [Install on Wix](/docs/brands/install/wix). ### How it works - The app injects click tracking automatically; no theme edits and nothing to paste. - Wix sends order webhooks to Rekomi; paid orders mint commissions on the pre-tax amount, and refunds adjust the commission automatically. - Affiliates get product links from your live store catalog and personal coupon codes created as real Wix coupons (cart-wide, up to 20 characters). - A campaign can reward affiliates with a Wix gift card instead of cash (see [Store credit rewards](/docs/brands/free-month-rewards#store-credit-for-shopify-wix-and-bigcommerce)). ### What Rekomi stores Rekomi keeps the app instance id that ties your Wix site to your workspace, the order ids, amounts, and currency of attributed orders, and the ids of the coupons it creates. No card data ever reaches Rekomi. ### Uninstalling Uninstalling the app stops tracking but does not cancel your Rekomi subscription; Wix keeps app subscriptions alive independently of the install, so cancel from the Wix subscriptions page in your Wix account. Reinstalling from the App Market re-arms your existing account, campaigns, and affiliates. ## BigCommerce Brands on **BigCommerce** connect natively through the **Rekomi Affiliate Marketing** app, live on the BigCommerce App Marketplace. Install the app and setup is done: it registers your order webhooks and injects the storefront tracking script, and the whole program runs inside your control panel. Your Rekomi subscription is billed through BigCommerce. ### Where to connect In your control panel go to **Apps > App Marketplace**, search "Rekomi" and click **Install**; approve the permissions, pick a plan on the Subscription tab (14-day free trial, billed through BigCommerce, paid BigCommerce plan required), and create your first campaign. The step-by-step guide is [Install on BigCommerce](/docs/brands/install/bigcommerce). ### How it works - Affiliate links track first-party on your storefront; the app's order-confirmation beacon records referred orders. - BigCommerce sends order and refund webhooks to Rekomi; paid orders mint commissions on the pre-tax total, and refunds claw back commission proportionally. Rekomi re-reads every order from the API before recording it, because BigCommerce webhooks carry no body signature. - Affiliates get personal coupon codes created as real BigCommerce promotions (cart-wide), and product links from your catalog. - A campaign can reward affiliates with BigCommerce store credit instead of cash (see [Store credit rewards](/docs/brands/free-month-rewards#store-credit-for-shopify-wix-and-bigcommerce)). ### What Rekomi stores The install token is encrypted at rest and never returned by the API. Rekomi keeps the store hash, the order ids, amounts and currency of attributed orders, and the ids of the promotions it creates. Rekomi never asks for checkout access and never edits your theme, products or orders. ## Lemon Squeezy Brands on **Lemon Squeezy** (a Merchant of Record) connect natively with **one API key**, with no webhook UI to configure and no relay to build. You paste the key into Rekomi; Rekomi validates it, generates a signing secret, creates the webhook on your store, and confirms it registered. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Lemon Squeezy**. Rekomi asks for the API key from Lemon Squeezy's Settings → API page and, if your account has multiple stores, lets you pick which to track. The step-by-step guide is [Install on Lemon Squeezy](/docs/brands/install/lemonsqueezy). ### How it works - You carry the affiliate referral into Lemon Squeezy checkout via `checkout[custom][rekomi_ref]` on checkout links (or `checkout_data.custom.rekomi_ref` on API-created checkouts). - Lemon Squeezy sends order, subscription, and refund events to the webhook Rekomi created; verified events record one-time sales and initial subscription orders as first conversions and renewals as recurring conversions, with the duplicate initial-payment event skipped so nothing is double counted. - Full and partial refunds reverse commission proportionally and automatically. ### What Rekomi stores Your Lemon Squeezy API key and the generated webhook signing secret are encrypted at rest and never returned by the API. Rekomi also keeps the store id, the webhook id (so disconnect deletes the webhook remotely), and connection-health timestamps. ### Key expiry Lemon Squeezy API keys expire after one year. Rekomi's connection health check detects the expiry, shows a reconnect banner, and emails you; pasting a fresh key restores the connection with history intact. ## Chargebee Brands on **Chargebee** (subscription billing on your own payment gateway) connect natively with a **site name plus a Read-Only API key** and one webhook you add in Chargebee. Chargebee webhooks are unsigned, so Rekomi generates Basic Authentication credentials for you; you paste them into Chargebee's webhook settings alongside your per-workspace URL. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Chargebee**. Rekomi asks for the site name (the `acme` in `acme.chargebee.com`) and a Read-Only API key from Chargebee's Settings → Configure Chargebee → API Keys and Webhooks page, validates the pair live, and shows the webhook URL and credentials to add in Chargebee (API version V2, "Exclude card information" checked). The step-by-step guide is [Install on Chargebee](/docs/brands/install/chargebee). ### How it works - You carry the affiliate referral onto the subscription via `meta_data.rekomi_ref` on API-created subscriptions, or a `cf_rekomi_ref` Subscription custom field passed as `subscription[cf_rekomi_ref]` through hosted checkout (or `product.data["cf_rekomi_ref"]` in the drop-in script). - Chargebee sends payment, subscription lifecycle, and refund events to the Rekomi webhook; authenticated events record first payments and one-time charges as first conversions and renewal payments as recurring conversions, de-duplicated by event id so retries never double count. - Full and partial refunds reverse commission proportionally and automatically. Chargebacks handled through Chargebee's Chargeback Management arrive as refunds and claw back too; disputes settled outside that feature do not reach Rekomi. ### What Rekomi stores Your Read-Only API key and the generated webhook Basic Authentication password are encrypted at rest and never returned by the API (the password is shown exactly once, when it is generated). Rekomi also keeps the site name, the derived live/test environment, and connection-health timestamps. ### Key health Chargebee API keys do not expire. Rekomi's connection health check detects a deleted or disabled key (or a renamed site), shows a reconnect banner, and emails you; pasting a fresh Read-Only key restores the connection with history intact. If you lose the webhook password, regenerate it from the setup page and update the Chargebee webhook; the old password stops working immediately. ## Polar Brands selling through **Polar** (Merchant of Record) connect natively with a single **Organization Access Token**. Rekomi validates the token, resolves your Polar organization, and creates the webhook endpoint in Polar itself; there is no manual webhook configuration on either side. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Polar**. Rekomi asks for an Organization Access Token created in Polar's Organization Settings → Developers with just the `organizations:read` and `webhooks:write` scopes (pick "No expiration"). The step-by-step guide is [Install on Polar](/docs/brands/install/polar). ### How it works - You carry the affiliate referral into checkout via `metadata.rekomi_ref` on API-created checkouts, or by appending `reference_id` to Polar checkout links (Polar copies it into the order's metadata). - Polar sends paid orders, subscription lifecycle events, and refunds to the webhook endpoint Rekomi created; signature-verified events record first purchases as one-time conversions and renewals as recurring conversions, de-duplicated by event id so retries never double count. - Commissions are computed on the net order amount: after discounts, excluding the tax Polar collects as Merchant of Record, and net of any customer-balance credit. Trials produce a $0 order at trial start; it never earns commission. When the referral is attributed, Rekomi records it as a $0 lead (visible in your funnel and leads views), and the commission comes from the first real charge. - Full and partial refunds reverse commission proportionally and automatically. Polar sends no webhook for disputes or chargebacks, so a lost dispute never reaches Rekomi on its own; invalidate the conversion manually and the commission reverses in the same audit trail. ### What Rekomi stores Your Organization Access Token and the Polar-generated webhook signing secret are encrypted at rest and never returned by the API. Rekomi also keeps the organization id and name, the endpoint id (so disconnect deletes the endpoint remotely), the environment, and connection-health timestamps. ### Token health Organization Access Tokens created with "No expiration" never lapse on a timer. Rekomi's connection health check detects a revoked token, a deleted endpoint, and Polar's endpoint auto-disable (Polar switches an endpoint off after repeated failed deliveries), shows a reconnect banner, and emails you with the right fix: re-enable the endpoint in Polar, or reconnect with a fresh token (which recreates the endpoint and rotates the signing secret). History stays intact either way. ## Recurly Brands on **Recurly** (subscription billing on your own payment gateway) connect natively with a **data region plus a read-only private API key** and one webhook endpoint you add in Recurly. Recurly XML webhooks are unsigned, so Rekomi generates Basic Authentication credentials for you; you paste them into Recurly's webhook endpoint settings alongside your per-workspace URL. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Recurly**. Rekomi asks for your data region (US or EU) and a read-only private API key from Recurly's Integrations → API Credentials page, validates the key live against your region (suggesting the other region if the key conclusively belongs there), and shows the webhook URL and credentials to add in Recurly (format XML, Basic Authentication). The step-by-step guide is [Install on Recurly](/docs/brands/install/recurly). ### How it works - You carry the affiliate referral onto the subscription via a `rekomi_ref` Subscription custom field (optionally mirrored on the Account), set through the API when creating subscriptions or purchases. Custom fields need Recurly Pro or Elite; the Starter plan does not support them, so new-customer attribution needs Pro or above. - Recurly sends paid invoices, subscription lifecycle events, refunds, and voids to the Rekomi webhook; authenticated events record purchase invoices as first conversions and renewal invoices as recurring conversions, de-duplicated so retries never double count. - Recurly webhooks never carry custom fields on subscription, account, payment, or invoice notifications, so on each subscription's **first payment** Rekomi reads `rekomi_ref` with your read-only key in one bounded API call; renewals are matched from your customer history with no API call. If Recurly's API is briefly down, Rekomi asks Recurly to retry the webhook (up to 10 attempts over roughly three days, with a manual per-notification Retry in your console for anything older). - Full and partial refunds reverse commission proportionally and automatically, per refund transaction; voided payments reverse fully. Card chargebacks claw back only through Recurly's early-access Chargeback Management feature (they arrive as refund-shaped events); disputes settled outside it do not reach Rekomi. ### What Rekomi stores Your read-only API key and the generated webhook Basic Authentication password are encrypted at rest and never returned by the API (the password is shown exactly once, when it is generated). Rekomi also keeps the region, the optional site subdomain, and connection-health timestamps. ### Key health Recurly API keys do not expire. Rekomi's connection health check detects a deleted or revoked key (revoking a Recurly user also deletes that user's API keys) or a region misconfiguration, shows a reconnect banner, and emails you; pasting a fresh read-only key restores the connection with history intact. If you lose the webhook password, regenerate it from the setup page and update the Recurly endpoint; the old password stops working immediately. ## Gumroad Brands selling through **Gumroad** (Merchant of Record for digital products and memberships) connect natively with **one click**: you approve Rekomi once on Gumroad's consent screen (read-only sales access) and Rekomi registers the sale, refund, dispute, and membership lifecycle notifications in your Gumroad account itself. Nothing is pasted into Gumroad on either side. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Gumroad**. The button runs the OAuth authorization; an advanced fallback accepts a pasted Gumroad access token (note that tokens created in Gumroad's Applications settings carry full account scope, which is why the button's sales-only grant is preferred). The step-by-step guide is [Install on Gumroad](/docs/brands/install/gumroad). ### How it works - You carry the affiliate referral on the product link via `?rekomi_ref=`; Gumroad preserves the landing URL's query parameters onto the sale notification. Affiliate links generated by Rekomi carry it automatically, and a link-decorator snippet covers Buy links on your own site. Membership renewals never carry the parameter; Rekomi credits them from the customer's first attributed sale. - Gumroad sends sales, refunds, disputes, and membership lifecycle events to a per-workspace Rekomi address. Gumroad does not sign these notifications, so Rekomi authenticates each delivery in layers: the secret per-workspace address (shown to workspace Owners only, rotated on every reconnect), a seller-identity binding, and a **readback** that re-reads every money event from Gumroad's API with your token and records amounts from that authoritative read, never from the raw notification. - Sales record in USD (Gumroad settles every charge in US dollars); first charges are one-time conversions and recurring membership charges are recurring conversions, de-duplicated so redeliveries never double count. Seller test purchases and $0 trial starts never create commissions. - Full refunds and chargebacks claw the commission back automatically. **Partial refunds do not reduce commissions automatically**; they are recorded for review, and a partial that completes to 100% claws back once, in full. - Gumroad retries failed deliveries only a few times inside roughly 14 minutes and drops timed-out deliveries entirely, so extended downtime windows are backfilled with the [Gumroad sales-history pull](/docs/brands/migrations/gumroad) (Settings → Migrate → Gumroad → Step 2, From = the gap start date). ### What Rekomi stores Your Gumroad access token is encrypted at rest and never returned by the API. Rekomi also keeps the seller id and display name, the ids of the six resource subscriptions it registered (so disconnect deletes them remotely), and connection-health timestamps. ### Token health Gumroad access tokens do not expire, but revoking the Rekomi app in Gumroad kills the token AND silently stops the notifications. Rekomi's connection health check detects the revocation (and a missing sale-notification registration), shows a reconnect banner, and emails you; clicking Connect Gumroad again re-registers all six feeds and rotates the webhook credentials, with history intact. ## Creem Brands selling through **Creem** (Merchant of Record for SaaS and digital products) connect natively with **one webhook plus two pastes**: you add a webhook in Creem's dashboard pointed at your per-workspace Rekomi URL with **all events selected**, then paste the webhook signing secret and an API key into Rekomi. The API key is used only to validate the connection and run a read-only health check (a key with Products read access is enough); your sales arrive through the signed webhook, never through the key. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Creem**. The page shows your webhook URL and the dashboard steps (Developers → Webhooks → Add Webhook, all events selected), then takes the signing secret and the API key; Rekomi validates the key live against Creem before accepting it, with specific errors for a revoked key, an under-scoped key, and a Test Mode key on a production connection. The step-by-step guide is [Install on Creem](/docs/brands/install/creem). ### How it works - You carry the affiliate referral in checkout metadata as `rekomi_ref`: set it when creating checkout sessions via Creem's API, or append `?metadata[rekomi_ref]=` to Creem payment links. The metadata persists onto the subscription, so renewals credit the same affiliate; a renewal that arrives without it falls back to the customer's attribution history. - Creem sends sales, subscription lifecycle events, refunds, and disputes to the Rekomi webhook; every delivery is signature-verified (Creem signs two ways, a legacy HMAC header and the standard-webhooks scheme, and Rekomi verifies whichever the delivery carries). The legacy scheme has no timestamp, so replay resistance on that path rests on event de-duplication; the standard scheme adds a 300-second freshness window. - One-time checkouts record as one-time conversions and subscription charges as initial or recurring conversions, de-duplicated by the underlying order and transaction ids so redeliveries and manual resends never double count. Commissions are computed on the pre-tax sale amount (Creem collects tax on top as Merchant of Record); trials produce a $0 lifecycle event that Rekomi never treats as money. - Refunds claw back commission proportionally on the pre-tax share, including pro-rata downgrade refunds. Disputes always claw back, and because Creem typically auto-refunds on chargeback, the clawback is capped at the original commission so the refund-plus-dispute double signal never double-claws. - Test Mode is isolated: events whose mode does not match the connection's environment are skipped, never recorded as money. ### What Rekomi stores Your API key and the webhook signing secret are encrypted at rest and never returned by the API. Rekomi also keeps the environment and connection-health timestamps; Creem has no store-identity endpoint, so no store id or name is stored. ### Key health Creem API keys do not expire. Rekomi's connection health check detects a deleted or regenerated key and a key that lost its Products read permission in a scope edit, shows a reconnect banner, and emails you; pasting a fresh key restores the connection with history intact. The check probes the key, not the webhook: Creem has no webhook management API, so a deleted endpoint is only detectable as event staleness, and the fix is re-adding the URL in Creem's dashboard with all events selected. ## Dodo Payments Brands selling through **Dodo Payments** (Merchant of Record for digital products and SaaS) connect natively with **one paste**: you paste an API key with write access enabled and Rekomi creates the webhook endpoint in your Dodo Payments account itself and fetches its signing secret for you. Nothing is pasted back into Dodo and you never handle the secret at all. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Dodo Payments**. The page takes one API key with write access enabled (read-only keys pass validation but cannot create webhooks, and the error says so); Rekomi validates the key live against Dodo, creates the endpoint with the exact event set it needs, and fetches the signing secret, all in one connect. The step-by-step guide is [Install on Dodo Payments](/docs/brands/install/dodo). ### How it works - You carry the affiliate referral in checkout metadata as `rekomi_ref`: set it when creating checkouts, subscriptions, or payments via Dodo's API, or append the flat `?metadata_rekomi_ref=` param to static Dodo payment links (Dodo copies flat `metadata_key=value` query params into metadata). Subscription metadata persists for the life of the subscription; a renewal that arrives without it falls back to the customer's attribution history. - Dodo sends payments, subscription lifecycle events, refunds, and disputes to the Rekomi webhook; every delivery is signature-verified (the standard-webhooks scheme with a 300-second freshness window), failing closed on anything malformed. - Every charge (one-time, first subscription charge, renewal, plan change, on-hold recovery) arrives as one payment event; Rekomi labels it initial or recurring by whether it has seen the subscription before, de-duplicated by the underlying payment, subscription, refund, and dispute ids so retries and manual redeliveries never double count. Commissions are computed on the **pre-tax** sale amount when Dodo reports the charge's tax split (as a Merchant of Record it normally does), and on the charge amount otherwise; refunds and disputes report tax-inclusive amounts, and every clawback is capped at what was credited. Trials produce no payment event until the first real charge. - Refunds claw back commission automatically, partials proportionally and capped at the original commission; a full refund reported without an amount claws back everything remaining. Disputes claw back when they open; a later win does not restore automatically (it is logged for manual adjustment). Payments in a currency Rekomi does not support are skipped and flagged, never guessed at. - Live mode and test mode are separate Dodo universes (separate hosts, keys, and webhooks), and events are additionally checked against the connected business before anything records. ### What Rekomi stores Your API key and the webhook signing secret are encrypted at rest and never returned by the API. Rekomi also keeps the business id, the id of the webhook endpoint it created (so disconnect deletes it remotely), the environment, and connection-health timestamps; Dodo exposes no account-name lookup, so no display name is stored. ### Key health Dodo API keys do not expire. Rekomi's connection health check probes the webhook endpoint it created (which verifies the key, the endpoint's existence, and whether it was disabled in Dodo's dashboard, in one call), shows a reconnect banner, and emails you, with distinct copy for a disabled endpoint (re-enable it in Dodo, or reconnect) versus a revoked key (create a fresh key with write access enabled and reconnect; Dodo has no secret-rotation API, so reconnect is always the recovery path). History stays intact across reconnects. ## Mollie Brands selling through **Mollie** (a payment service provider for businesses in the EEA, Switzerland, and the UK) connect natively with **one paste**: you paste an advanced access token with seven permissions (six read permissions plus `webhooks.write`) and Rekomi creates one signed webhook in your Mollie organization and stores its signing secret for you. Nothing is pasted back into Mollie and you never handle the secret at all. This tracks your sales (so affiliates get credited) and is independent of how you pay for Rekomi. ### Where to connect Setup → Connect payment gateway → **Connect Mollie**. The page takes one advanced access token, created by an Admin under Developers > API keys with `webhooks.read`, `webhooks.write`, `payments.read`, `refunds.read`, `profiles.read`, `organizations.read`, and `customers.read` ticked (a standard API key cannot manage webhooks and is refused, and the error says so). Create the token for all website profiles in the organization and for live mode; Mollie answers a token limited to a single profile with a 403 on the connect calls. Rekomi validates the token live against Mollie, asks you to pick a website profile if the token sees several, and creates the webhook with the events it needs, all in one connect. The webhook is named "Rekomi" followed by a short id for your workspace, because Mollie needs webhook names to be unique inside an organization. The step-by-step guide is [Install on Mollie](/docs/brands/install/mollie). ### How it works - You carry the affiliate referral as `metadata.rekomi_ref` when creating payments and subscriptions through Mollie's API; Mollie forwards subscription metadata to every payment the subscription generates, so renewals arrive already stamped, and a renewal without it falls back to the customer's attribution history. Mollie has no coupon codes of its own, so a per-affiliate code is passed as `metadata.rekomi_coupon`; Rekomi reads it on one-time and first payments only, and renewals credit the same affiliate through customer and subscription history, so a coupon's commission override applies to the first charge rather than to every cycle. For signup-first SaaS, Rekomi matches the payment to a lead the tracking script captured on your site (email capture on the signup form) by the buyer's email: from `billingAddress.email` on the payment, or, when the payment carries none but belongs to a Mollie customer, from that customer record through `customers.read`. With email capture on the signup form, subscription sales attribute even without the metadata stamp, while the stamp stays the precise path. A token created before `customers.read` was added to the list does not carry it, so a brand that connected earlier creates a fresh advanced access token with all seven permissions and reconnects (history is preserved), and passes `billingAddress.email` on the payments it creates itself until then. An affiliate buying with their own email is not credited. - Mollie sends payments, refunds, and chargebacks to the Rekomi webhook as signed full snapshots; every delivery is signature-verified (HMAC-SHA256 over the raw body with the webhook's secret), failing closed on anything malformed. Refund and chargeback events are still in beta on some Mollie accounts, so Rekomi also checks payments, refunds, and chargebacks every 30 minutes and records anything the webhook did not deliver; the two paths share the same keys, so nothing double counts. - Every paid payment (one-time, first subscription charge, renewal) arrives as one `payment.paid` event, once per payment id; Mollie labels each payment one-off, first, or recurring itself. A first payment below one unit of its currency (Mollie's 0.00 or 0.01 EUR mandate-verification charge) is never a sale and never starts the recurring window. Commissions are computed on the pre-tax total when the payment carries order lines with VAT, otherwise on the charge amount (Mollie is a payment service provider, not a merchant of record, so a payment carries no tax field of its own). Mollie sends no subscription lifecycle events, so churn is inferred when a subscription stops producing paid payments. - Refunds claw back commission per refund once the money goes back to the customer, partials proportionally and capped at the original commission. Chargebacks claw back when they are received; a later reversal does not restore automatically (it is logged for manual adjustment). All 28 Mollie currencies are inside Rekomi's allowlist. - One website profile per workspace: payments from other profiles in the same organization are skipped with a log entry. Live mode and test mode, like the website profile, are checked on every payment before it records; refunds and chargebacks bind through the payment they belong to, so a refund or chargeback only ever applies to a payment this workspace recorded. ### What Rekomi stores Your advanced access token and the webhook signing secret are encrypted at rest and never returned by the API. Rekomi also keeps the organization id, the bound website profile id, the id of the webhook it created (so disconnect deletes it remotely), the granted event list, the mode, the cursors for the 30-minute check, and connection-health timestamps. ### Key health Rekomi's connection health check reads the webhook it created (which verifies the token, the webhook's status, and its event list in one call), shows a reconnect banner, and emails you, with distinct copy for a blocked or disabled webhook (re-enable it in Mollie under Developers > Webhooks, or reconnect) versus a revoked token (create a fresh advanced access token and reconnect). Mollie has no secret-rotation API, and rolling the secret in Mollie's dashboard invalidates the stored one within a day, so reconnect is always the recovery path. History stays intact across reconnects. --- # Manage affiliates Source: https://rekomi.com/docs/brands/manage-affiliates > Approval queue, per-affiliate commission tracking and overrides, status flow, and archiving. --- title: Manage affiliates description: Approval queue, per-affiliate commission tracking and overrides, status flow, and archiving. --- Once applications start coming in, `/dashboard/affiliates` is where you live. Three top-level tabs: **All**, **Pending**, and **Invites**. ![The Affiliates page on the All tab, with referrals, commissions, revenue, and status per affiliate](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/affiliates-all.png?v=5) ## Pending tab The Pending tab shows everyone with `Status = Pending`. For each row you see: ![The Pending tab with two applications and Approve and Reject actions on each row](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/affiliates-pending.png?v=5) - The applicant's name and email - Their assigned tracking slug - When they applied Click through to the affiliate's detail page and use the View application button in the header to read their full answers (website, audience, why they're interested). Two actions on each row: - **Approve**: moves them to `Approved` and sends an approval email (template `application_approved`); their tracking slug was assigned when they applied. The email includes a single-use "Get your link" button that signs the affiliate straight into `/a` with no signup form. The magic-link expires in 14 days. - **Reject**: opens a confirmation dialog. Optionally include a reason and choose whether to notify the applicant. Notify is on by default; uncheck for spam or abusive applicants where a reply would invite retry. The email template (when notify is on) is `application_rejected`. The audit log captures both the notify decision and the reason regardless of choice, so silent rejections are still fully traceable. ![The reject dialog with an optional reason field and the notify-applicant checkbox](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/reject-dialog.png?v=4) If your campaign has `autoApprove = true`, you will rarely see this tab populated. Applications skip Pending and land directly in Approved. ## All tab Lists every affiliate in every campaign on your account, searchable by name or email. Each row: - Affiliate (name + email) - When they joined - Referrals (clicks, leads, conversions) - Commissions (unpaid and paid) - Revenue (gross and net) - Status (Pending, Approved, Paused, Rejected, or Banned). Approved affiliates who have not signed in yet show as "Invited"; once they claim their account the badge reads "Active". Copy an affiliate's slug from the row menu. Click any row to open the affiliate detail. ## Affiliate detail When the affiliate has a payout due, a **Pay out** button with the amount appears in the header; it opens the payout dialog with them pre-selected (see [Per-affiliate payouts](/docs/brands/payouts#per-affiliate-payouts)). Per-affiliate view shows their full conversion history (sales, leads and clicks), their custom commission (if any), their tracking link, and admin actions: ![The affiliate detail page on the Payouts tab, with unpaid and paid totals, due commissions, and payout history](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/affiliate-detail.png?v=5) - **Pause**: temporarily stop crediting them. Their existing pending conversions stay; no new ones are credited. Use this for affiliates you want to revisit later. - **Ban**: permanently stop new conversions from attributing to them and lock them out of the affiliate dashboard. Their links still take visitors to your site (they point at your site directly), but no new commissions accrue. Commissions already earned pay out on schedule. - **Remove from campaigns**: take them off your roster entirely. This deletes the affiliate record and its pending history, so prefer Ban when you want the paper trail. - **Set custom commission**: override the campaign default and any tier match. Useful for negotiated deals with top creators. - **New commission**: the button on the Payouts tab opens the Add sale or bonus dialog with this affiliate preselected, to record a sale that happened outside your connected billing or give a one-off bonus commission. Rekomi computes the commission from the campaign's rules unless you override it. See [How sales are created](/docs/brands/sales#how-sales-are-created). - **Rename their tracking code**: the Tracking card shows the affiliate's link for this campaign, and the pencil lets you change its code (3 to 64 letters, numbers, or dashes). The old code is kept as an extra link, so everything the affiliate already shared keeps working. The affiliate's account-level code is theirs alone; only they can change that one. The detail view opens on the **Payouts** tab, where you manage everything money: what they're owed, their Due, In grace, and Paid commissions, and the ability to **mark selected commissions as paid** or **pay all due** in one click, plus their full payout history. See [Per-affiliate payouts](/docs/brands/payouts#per-affiliate-payouts). ## Status flow ``` Pending → Approved → Paused → Approved ↓ ↓ Rejected Banned ``` `Pending` is the initial state for applications when `autoApprove = false`. `Approved` is the working state. `Paused` is a soft hold. `Banned` permanently stops new attribution. `Rejected` means the application never got off the ground. ## Tax form prompts If the payments to a U.S.-based affiliate reach the IRS reporting threshold in a calendar year ($2,000 for payments made in 2026 onward), a 1099-NEC is due; Rekomi (Uplup Inc.) is the payer of record and files it, so you never do. The threshold counts payments made during the year, not commissions earned. When you pay through Stripe, affiliates provide their tax details (W-9 for U.S. affiliates, W-8BEN or W-8BEN-E for non-U.S.) directly during Stripe payout onboarding, and Stripe collects and holds those forms. Affiliates on PayPal or bank deposit upload a W-8 to Rekomi instead. See [Tax forms](/docs/brands/tax-forms) for the full compliance flow. ## Bulk operations Bulk operations work from both the dashboard and the API. The affiliates table at `/dashboard/affiliates` has row-level checkboxes; once you select one or more rows, a toolbar appears with `Approve`, `Pause`, and `Delete` actions. Bulk actions require the Owner role and fan out to `/api/affiliates/bulk-action`, capped at 200 affiliates per request so large batches stay fast; send several requests for bigger rosters. ## Notifications affiliates receive Rekomi sends affiliates a full catalog of automated lifecycle emails on your behalf: the application-received confirmation, the welcome email when you approve (which carries their tracking link and the "Get your link" button), and payout notices when money starts moving and when it arrives, among others. Each email has a switch on the campaign's **Emails** tab (a campaign follows your account-wide defaults until you flip a switch there); a few opt-in ones (like the commission-earned email) stay off until you enable them. See [Emails](/docs/brands/emails) for the full list. All of these go through Resend with DKIM-signed delivery from `mail.rekomi.com`, or from your own domain once you set up a [custom sending domain](/docs/brands/sending-domain). ## Sub-affiliate recruiting income If your campaign has [sub-affiliate recruiting](/docs/brands/sub-affiliate-recruiting) enabled, override income shows up on the recruiter's affiliate-detail view as a separate line per sale. Override rows have `amountCents = 0` and a positive `commissionCents`, with `attributionMethod = "sub_affiliate_override"`. Refunds on the parent sale proportionally claw back the override automatically. Override income counts toward the recruiter's 1099-NEC threshold. --- # Migrating from another affiliate platform Source: https://rekomi.com/docs/brands/migrating-from-other-platforms > Move your affiliate or referral program to Rekomi without changing affiliate URLs. The compatibility matrix covering more than 20 platforms, the bulk-invite flow, and the custom-param config for niche cases. --- title: Migrating from another affiliate platform description: Move your affiliate or referral program to Rekomi without changing affiliate URLs. The compatibility matrix covering more than 20 platforms, the bulk-invite flow, and the custom-param config for niche cases. --- The single biggest fear about switching affiliate platforms is having to email every affiliate to update their links. With Rekomi, you almost never have to. Rekomi natively recognizes the attribution query params used by more than 20 major affiliate platforms. As long as your previous platform used one of the 21 query-param conventions in the table below, **your affiliates' existing referral links keep working unchanged** when you swap out the JavaScript snippet on your site. Same slugs, same URLs, no affiliate outreach. ## How affiliate URLs actually work (the boring truth) Almost every affiliate platform on the market produces links that look like one of these: ``` https://your-brand.com/?via=alex https://your-brand.com/?ref=alex https://your-brand.com/?fpr=alex https://your-brand.com/?aff=alex ``` The platform's JavaScript snippet on your site reads the query param (`via`, `ref`, `fpr`, `aff`, etc.) and persists the slug (`alex`) as the referring affiliate. The slug + the param name are the only two things that determine whether the link still attributes after migration. So the migration math is: 1. Same slug + same param convention → link works as-is, no affiliate sees any difference. 2. Same slug + different param → one redirect line on your reverse proxy fixes it. 3. Different slug → affiliates need a new link. Most migrations are case 1. ## Native compatibility matrix | Platform | Their query param | Works on Rekomi as-is? | |---|---|---| | Rewardful | `?via=` | Yes | | Affonso | `?via=` (also `?ref=`, `?aff=`, `?atp=` or `?affonso=` per program) | Yes. Roster + leads + history import yourself via the [Affonso importer](/docs/brands/migrations/affonso) | | Tapfiliate | `?ref=` | Yes | | FirstPromoter | `?fpr=` | Yes | | Refersion | `?rfsn=` | Yes. Roster + history import yourself via the [Refersion importer](/docs/brands/migrations/refersion) | | Affiliately | `?aff=` | Yes | | LeadDyno | `?afmc=` | Yes. Roster + leads + history import yourself via the [LeadDyno importer](/docs/brands/migrations/leaddyno) | | Tolt | `?aff=` | Yes | | PromoteKit | `?via=` | Yes. Roster + leads + history import yourself via the [PromoteKit importer](/docs/brands/migrations/promotekit) | | Partnero | `?aff=` (also `?ref=` / `?via=` per program) | Yes. Roster + leads + history import yourself via the [Partnero importer](/docs/brands/migrations/partnero) | | Lemon Squeezy | `?aff=` | Yes (on your own site; see note below) | | Post Affiliate Pro | `?a_aid=` (the classic `#a_aid=` fragment links carry over too) | Yes. Roster + transaction history import yourself via the [Post Affiliate Pro importer](/docs/brands/migrations/postaffiliatepro) | | AffiliateWP | `?ref=` | Yes | | UpPromote | `?sca_ref=` | Yes. Roster + history import yourself via the [UpPromote importer](/docs/brands/migrations/uppromote) | | GoAffPro | `?ref=` (goaff.pro and bit.ly short links do not carry over) | Yes. Roster + history import yourself via the [GoAffPro importer](/docs/brands/migrations/goaffpro) | | BixGrow | `?bg_ref=` (real Shopify discount codes also keep crediting) | Yes. Roster + history import yourself via the [BixGrow importer](/docs/brands/migrations/bixgrow) | | Friendbuy | `?ref=` | Yes | | Reditus | `?via=` | Yes | | Dub Partners | `?via=` | Yes | | iDevAffiliate | `?idev=` | Yes | | Talkable | `?mbsy=` | Yes | | Awin | `?awc=` | Yes | | ShareASale | `?sscid=` | Yes | | CJ Affiliate | `?cjevent=` | Yes | | Impact | `?irclickid=` | Yes (post-redirect URL persistence). Roster + history import yourself via the [impact.com importer](/docs/brands/migrations/impact) | | PartnerStack | `?partner_key=` (query mode) | Yes for query mode; path-based mode needs a redirect rule | | Trackdesk | link params on your own domain | Partial: direct links keep loading, but attribution uses the affiliate's Rekomi link; public Affiliate IDs import as slugs via the [Trackdesk importer](/docs/brands/migrations/trackdesk) | | ReferralCandy | not param-based; refr.cc short links do not carry over | Partial: continuity comes from FRIEND- prefixed store coupon codes re-scoped as Rekomi coupons (Shopify only). Roster import yourself via the [ReferralCandy importer](/docs/brands/migrations/referralcandy) | | Shopify Collabs | not param-based; vanity creator links do not carry over | Partial: continuity comes from native Shopify discount codes re-scoped in Rekomi (coupon-based attribution). Roster import yourself via the [Shopify Collabs importer](/docs/brands/migrations/collabs) | | Everflow | not param-based; machine-generated path tokens on Everflow's tracking domain do not carry over | Partial: continuity comes from coupon codes re-created in Rekomi. Roster + click totals + up to 365 days of history import yourself via the [Everflow importer](/docs/brands/migrations/everflow) | | Voluum / generic CPA | `?affid=` | Yes | | In-house and legacy tools | `?affiliate_id=` | Yes | | Cellxpert | `?affcode=` | Yes | If your platform is not in this list but uses a recognizable query-param convention, you can add it as a custom param under **Settings > Attribution** in the Rekomi dashboard. ![The attribution settings page with the recognized default params and the custom additional-params field](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/attribution-settings.png?v=5) ## The 3-step migration Assuming your previous platform uses one of the 21 recognized params: ### 1. Import affiliates with slugs preserved Two ways to bring your existing affiliates over with their slugs intact: **Option A: the dedicated importers (Settings > Migrate).** Pick your source platform. ![The migration source picker under Settings > Migrate with a tile for each supported source platform](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/migrate-source-picker.png?v=4) For **Rewardful, Affonso, FirstPromoter, and Tolt** the primary path is a one-key **API pull** that imports the roster (slugs preserved) plus leads, lifetime click counts, and commission history in one run; a CSV upload remains as a fallback. **Lemon Squeezy** is also an API pull (roster only; their API exposes no history or clicks). **Tapfiliate, PartnerStack, and Dub Partners** are CSV uploads. **Gumroad** is a CSV upload for the roster with an API pull for sales history; slugs are never preserved there (Gumroad `/a/` links use opaque ids that only route inside Gumroad, so no slug exists to carry). Either way the importer reads the source platform's slug automatically: | Source | Slug source we read | |---|---| | Rewardful | `token`/`Tokens` (or extracted from the `link`/`Links` URL); the API pull uses each affiliate's link token | | Affonso | `tracking_id` (or `tracking_parameter`, or extracted from a link); the API pull uses each affiliate's tracking id | | Tapfiliate | extracted from the `Program_referral_link` URL (the export has no dedicated code column; a hand-built `referral_code` column also works) | | FirstPromoter | `default_ref_id` (v1) or `ref_token`/`ref_id` (v2); the API pull uses `ref_token` | | Dub Partners | not in Dub's roster export (partners can hold many short links), so affiliates get fresh slugs; a hand-built `partner_link` column is parsed when present | | Tolt | the API pull reads each partner's first link from Tolt's Links resource; on CSV, `referral_code` or a `?aff=` link when present | | Lemon Squeezy | Your pasted `email,aff_code` mapping (their API omits the codes; see note below) | | Gumroad | none: `/a/` links use opaque ids that cannot re-attribute, so affiliates get fresh slugs | | PartnerStack | `partner_key` when your file carries it (the Partner performance report export does; the standard Export-records bundle does not) | A green "Affiliate links preserved automatically" badge appears next to the platform name on the upload screen when the parser will read slugs. Every imported affiliate keeps their existing slug; their existing public referral URL (e.g. `brand.com/?via=alex`) continues to attribute on Rekomi unchanged. **Lemon Squeezy `?aff=` continuity.** Lemon Squeezy affiliate links use `?aff=`, which is already in Rekomi's recognized param family, so the param side needs no configuration. The slug side is manual: Lemon Squeezy has no affiliate CSV export and their API omits each affiliate's `?aff=` code (it is only visible on that affiliate's profile page in their dashboard), so the [Lemon Squeezy importer](/docs/brands/migrations/lemonsqueezy) accepts an optional pasted `email,aff_code` mapping for the codes you want preserved. Continuity applies to links landing on your own site where the Rekomi snippet runs; links that point straight at your `*.lemonsqueezy.com` storefront cannot be carried over by any migration tool. **Option B: Bulk invite with slugs (Affiliates page > Invite > Bulk).** Toggle the paste format to `email, slug, name` and paste one row per affiliate: ``` alex@example.com, alex, Alex Example jamie@example.com, jamie-r, Jamie Recommends ``` We send each invite as usual; the accept flow uses the slug you supplied. If a slug is taken or invalid, that affiliate still gets an invite but with a fresh random slug. The response surfaces those rows in a `slugConflicts` array so you can re-send or re-think. **Option C: MCP install assistant.** Ask the connected AI assistant: "I'm migrating from FirstPromoter. Here's my affiliate list." Hand it the CSV; the assistant calls `bulk_invite_affiliates` with the `recipients` shape (per-row email + slug + name) under the hood and reports back per-row outcomes. Single invites are free on every plan including Trial. Bulk invites require Starter or higher with an active paid subscription; the migration importers work on Starter or higher, including during a trial. ### What happens when a slug is taken Affiliate slugs are globally unique across Rekomi. If you try to migrate an affiliate with slug `alex` and another brand somewhere on Rekomi already has an affiliate with slug `alex`, your import: 1. Still creates the invite / affiliate row. 2. Falls back to a fresh random 12-char slug for that one affiliate. 3. Bulk invites report those rows in the response's `slugConflicts` field; CSV imports fall back silently, so scan the import preview for affiliates whose slug changed. Either way you can re-send under a different slug, or ask that affiliate to update their link. In practice this is rare for migrating brands because most affiliate slugs are name-based and the global namespace is sparse. Brands with very generic slugs (e.g. an affiliate literally named `affiliate1`) are the most likely to hit a conflict. ### 2. Swap out the JavaScript snippet on your site Replace your old affiliate-platform script tag with the Rekomi loader. The snippet is shown on every install recipe page under **Setup > Install tracking script** in the dashboard. ### 3. Connect Stripe (or set up coupon attribution) If your previous platform was Stripe-webhook-driven (most are), connect Stripe in the **Connect payment gateway** step (`/dashboard/setup/connect-sales`) and conversion attribution flows automatically. If you were using coupon-code attribution previously, mint per-affiliate Stripe promotion codes under each affiliate's detail page. ### 4. (Optional) Import historical earnings For Rewardful, Affonso, FirstPromoter, and Tolt, the API pull in step 1 already brought leads, lifetime click counts, and commission history across; nothing more to do unless you want unpaid balances made payable. For the CSV sources, upload your platform's conversions/commissions export under **Settings > Migrate > Step 2** to bring over each affiliate's past conversions and commissions so their stats are not reset to zero (rows the export explicitly marks as leads import as zero-money lead records). Already-paid commissions import as locked history (never paid again); unpaid ones are queued for a Rekomi payout only if you explicitly authorize it on the preview screen. When the export includes a customer's Stripe customer/subscription id and your Stripe is connected, we seed the attribution so existing subscriptions keep paying the same affiliate on renewals. Every history import shows a dry-run preview (including exactly how much, if anything, Rekomi would pay) before you commit, and re-running is safe. See the [migrations overview](/docs/brands/migrations) for details. That's it. Affiliates never had to update a single link, and their history comes with them. ## When you DO need a redirect rule A few cases need one extra line on your server: ### Niche query-param platforms If your previous platform used a param not in the recognized 19 (e.g. an internal tool that used `?our_aff_id=` or similar), add it as a custom param in **Settings > Attribution**: - Comma-separated additional params, each 2-32 chars, lowercase letters / digits / underscore / hyphen - Up to 10 custom entries, 256 chars total After saving, the new params are recognized by the loader on the next page load. No snippet re-paste, no affiliate outreach. ### Path-based platforms (PartnerStack legacy, Impact) PartnerStack and Impact often produce path-based links like `partner.brand.com/refer/alex` or `offers.impact.com/c/12345/alex`. These can't be migrated by query-param recognition alone because the slug is not in the URL's query string. Options: 1. **CNAME the old subdomain to your origin + add a path rewrite.** Example for Cloudflare Workers: ```js addEventListener("fetch", (event) => { const url = new URL(event.request.url); const m = url.pathname.match(/^\/refer\/([a-z0-9-]+)\/?$/); if (m) { return event.respondWith( Response.redirect(`https://your-brand.com/?via=${m[1]}`, 302), ); } }); ``` 2. **Email affiliates once with new links.** For high-volume affiliates this is friction, but if you only have a few partners on a path-based platform, it can be the cleanest solution. ### Custom platforms with completely different conventions If your old platform did something exotic (POST-based attribution, fingerprinting, etc.), reach out to support@rekomi.com and we'll help you scope a custom integration path. ## Why your previous platform pushed friction at you Most affiliate platforms have a financial incentive to make leaving them feel painful. Inbound "drop-in migration" help is generously documented while outbound export paths stay thin; Rewardful, for example, offers no do-it-yourself referrals export. Impact's "media partner migration team" is a sales-friction tax built into the product. PartnerStack's "Concierge migration" service is the same thing in different packaging. We don't have that incentive. The more brands that switch in, the better Rekomi gets. Drop-in compatibility with the 19 most-common params is a one-time engineering cost we paid up front so you never have to. ## Programmatic migration via the MCP install assistant If you have an AI client connected to Rekomi via MCP (Claude Desktop, Claude Code, Cursor, Continue, Zed, ChatGPT Connectors), ask it: > "I'm migrating from FirstPromoter. Here's my affiliate list as a CSV. Help me move everything over." The assistant calls: 1. `get_billing_state` to confirm your plan tier covers bulk-invite. 2. `get_attribution_params` to confirm your source platform's param is in the defaults (FirstPromoter's `?fpr=` is). 3. `bulk_invite_affiliates` to send the branded invite emails. 4. Optionally `update_attribution_params` if you need a custom param. The entire migration runs in chat. No CSV upload UI, no copy-paste between tabs. ## See also - [MCP server](/docs/developers/mcp): the AI install assistant and more than 50 other tools - [Pixel tracking](/docs/developers/pixel-tracking): the loader script behavior + recognized params - [Bulk affiliate invites API](/docs/developers/reference#affiliate-invites): the underlying endpoint the install assistant uses --- # Multi-currency Source: https://rekomi.com/docs/brands/multi-currency > HomeCurrency, FX normalization, USD-settled payouts, and how the IRS 1099-NEC handles non-USD earnings. --- title: Multi-currency description: HomeCurrency, FX normalization, USD-settled payouts, and how the IRS 1099-NEC handles non-USD earnings. --- If your affiliates sell in more than one currency (or you sell in EUR but report in USD), Rekomi handles the conversion math for you. Conversions are always stored in the currency they came in as. Your dashboards normalize everything to a single **home currency** so a "$120,400 this quarter" KPI tile means the same thing whether your sales came in USD, EUR, or JPY. ## What is HomeCurrency `HomeCurrency` is your organization's reporting currency. It defaults to **USD** for every new account. It affects: - The total on dashboard KPI tiles (summary cards, leaderboards, time series charts). - The "home" column in tables that also show the native amount (e.g. a $99 USD row and a 89 EUR row both appear with a converted home-currency value next to them). - Reports + CSV exports: a summary row in your home currency, plus per-row native amounts so nothing is lost in translation. What it does **not** affect: - The currency of any existing conversion. A 89 EUR sale stays a 89 EUR sale on disk. - The currency of any existing payout. Payouts settle in USD (see below); already-created payouts keep the currency they were created in. Changing HomeCurrency is a display recomputation, not a data migration, with one guard: per-click rewards, per-lead bounties and campaign budgets are stored in your home currency, so the change is refused while any active campaign carries one of them. Clear those amounts (or archive the campaign), change the currency, then set them again in the new currency. ## How to change it `/dashboard/settings/billing` has a **Home currency** card under the **Currency** tab. ![The Home currency card in billing settings](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/billing-home-currency.png?v=4) - **Pro or Enterprise plan required to change it.** New workspaces start on USD. Starter and Growth workspaces cannot pick a non-USD home currency; a workspace that set one while on Pro keeps it after a downgrade and needs Pro again to change it. Workspaces trialing the Pro plan can change it during the trial; Starter and Growth trials cannot. - Only workspace owners and admins can change the home currency. - Pick from the supported currency list. Save. The dashboard re-renders against the new home currency on next page load. - Changes are audit-logged (who, when, old → new) under **Settings > Audit Log**. - The change re-renders all dashboard rollups, past and future, in the new home currency; each conversion is still valued at the reference rate on its own date, never at today's rate. Historical conversions keep their original native currency. ## FX rates and refresh cadence - **Sources: the European Central Bank and Xe.** Major currencies use the ECB's published daily reference rates (via the [Frankfurter](https://api.frankfurter.app) API); every other supported currency uses mid-market rates from [Xe](https://www.xe.com), the same class of no-spread reference rate. - **Refresh: daily** at 06:00 UTC via a background job. The full matrix of supported currencies is stored locally so the dashboard never has to call out to FX on a page render. - **Rate dates:** each stored rate carries the date its source published it. The ECB fixes its rates in the afternoon (CET) and the next 06:00 UTC refresh picks them up, so the newest ECB rate available on any given day is dated the previous business day (or the Friday before, over a weekend); Xe rates carry the day they were locked in. Wherever Rekomi records a rate date, that recorded date is the one that governs. - **Rate used for a conversion:** the reference rate on the conversion's `CreatedAt` date. This is stable: re-loading a six-month-old dashboard view shows the same converted total it showed six months ago. - **Stale-cache fallback:** if the daily refresh fails (a rate-provider outage, our job error), the previous day's rates are used and an alert fires to Rekomi's on-call. Your dashboard keeps working. A rate is served for at most 7 days past its publication date; beyond that, affected amounts are held out of the home-currency totals rather than being valued at old math, and listed per currency in their native amounts under the Reports KPI tiles, on Insights (revenue and commission separately), in the campaign budget card, and in the run-payout dialog; they return to the totals automatically when rates resume. - **No spread, no fee.** These are published mid-market reference rates, used exactly as published: Rekomi adds no margin to the rate and charges nothing for the conversion math. This normalization is reporting-only and never moves real money. Where an actual currency conversion happens in the money flow, it is the payment processor's own cost, never Rekomi's: an international funding debit and a bank-deposit payout arriving in the affiliate's local currency are passed through at cost and itemized, and a Stripe Express or PayPal recipient who converts the USD they receive pays that provider's own rate and conversion charge on their side; see [funding your payouts](/docs/brands/payouts#funding-your-payouts) and the [affiliate withdrawal fee](/docs/affiliates/payouts#withdrawal-fee). ## Supported currencies 135+ currencies are supported: every currency Stripe can present a price in, plus the three-decimal Gulf currencies (BHD, JOD, KWD, OMR, TND) common on other gateways. The full list, with each currency's flag, code, name, symbol, and decimal places: | | Code | Currency | Symbol | |---|---|---|---| | 🇦🇪 | AED | UAE Dirham | د.إ | | 🇦🇫 | AFN | Afghan Afghani | ؋ | | 🇦🇱 | ALL | Albanian Lek | L | | 🇦🇲 | AMD | Armenian Dram | ֏ | | 🇦🇴 | AOA | Angolan Kwanza | Kz | | 🇦🇷 | ARS | Argentine Peso | AR$ | | 🇦🇺 | AUD | Australian Dollar | A$ | | 🇦🇼 | AWG | Aruban Florin | ƒ | | 🇦🇿 | AZN | Azerbaijani Manat | ₼ | | 🇧🇦 | BAM | Bosnia-Herzegovina Convertible Mark | KM | | 🇧🇧 | BBD | Barbadian Dollar | Bds$ | | 🇧🇩 | BDT | Bangladeshi Taka | ৳ | | 🇧🇭 | BHD | Bahraini Dinar | .د.ب | | 🇧🇮 | BIF | Burundian Franc | FBu | | 🇧🇲 | BMD | Bermudian Dollar | BD$ | | 🇧🇳 | BND | Brunei Dollar | B$ | | 🇧🇴 | BOB | Bolivian Boliviano | Bs | | 🇧🇷 | BRL | Brazilian Real | R$ | | 🇧🇸 | BSD | Bahamian Dollar | B$ | | 🇧🇼 | BWP | Botswanan Pula | P | | 🇧🇾 | BYN | Belarusian Ruble | Br | | 🇧🇿 | BZD | Belize Dollar | BZ$ | | 🇨🇦 | CAD | Canadian Dollar | CA$ | | 🇨🇩 | CDF | Congolese Franc | FC | | 🇨🇭 | CHF | Swiss Franc | CHF | | 🇨🇱 | CLP | Chilean Peso | CL$ | | 🇨🇳 | CNY | Chinese Yuan | CN¥ | | 🇨🇴 | COP | Colombian Peso | CO$ | | 🇨🇷 | CRC | Costa Rican Colon | ₡ | | 🇨🇻 | CVE | Cape Verdean Escudo | Esc | | 🇨🇿 | CZK | Czech Koruna | Kč | | 🇩🇯 | DJF | Djiboutian Franc | Fdj | | 🇩🇰 | DKK | Danish Krone | kr | | 🇩🇴 | DOP | Dominican Peso | RD$ | | 🇩🇿 | DZD | Algerian Dinar | د.ج | | 🇪🇬 | EGP | Egyptian Pound | E£ | | 🇪🇹 | ETB | Ethiopian Birr | Br | | 🇪🇺 | EUR | Euro | € | | 🇫🇯 | FJD | Fijian Dollar | FJ$ | | 🇫🇰 | FKP | Falkland Islands Pound | £ | | 🇬🇧 | GBP | Pound Sterling | £ | | 🇬🇪 | GEL | Georgian Lari | ₾ | | 🇬🇮 | GIP | Gibraltar Pound | £ | | 🇬🇲 | GMD | Gambian Dalasi | D | | 🇬🇳 | GNF | Guinean Franc | FG | | 🇬🇹 | GTQ | Guatemalan Quetzal | Q | | 🇬🇾 | GYD | Guyanese Dollar | G$ | | 🇭🇰 | HKD | Hong Kong Dollar | HK$ | | 🇭🇳 | HNL | Honduran Lempira | L | | 🇭🇹 | HTG | Haitian Gourde | G | | 🇭🇺 | HUF | Hungarian Forint | Ft | | 🇮🇩 | IDR | Indonesian Rupiah | Rp | | 🇮🇱 | ILS | Israeli New Shekel | ₪ | | 🇮🇳 | INR | Indian Rupee | ₹ | | 🇮🇸 | ISK | Icelandic Krona | kr | | 🇯🇲 | JMD | Jamaican Dollar | J$ | | 🇯🇴 | JOD | Jordanian Dinar | د.ا | | 🇯🇵 | JPY | Japanese Yen | ¥ | | 🇰🇪 | KES | Kenyan Shilling | KSh | | 🇰🇬 | KGS | Kyrgystani Som | с | | 🇰🇭 | KHR | Cambodian Riel | ៛ | | 🇰🇲 | KMF | Comorian Franc | CF | | 🇰🇷 | KRW | South Korean Won | ₩ | | 🇰🇼 | KWD | Kuwaiti Dinar | د.ك | | 🇰🇾 | KYD | Cayman Islands Dollar | CI$ | | 🇰🇿 | KZT | Kazakhstani Tenge | ₸ | | 🇱🇦 | LAK | Laotian Kip | ₭ | | 🇱🇧 | LBP | Lebanese Pound | ل.ل | | 🇱🇰 | LKR | Sri Lankan Rupee | Rs | | 🇱🇷 | LRD | Liberian Dollar | L$ | | 🇱🇸 | LSL | Lesotho Loti | L | | 🇲🇦 | MAD | Moroccan Dirham | د.م. | | 🇲🇩 | MDL | Moldovan Leu | L | | 🇲🇬 | MGA | Malagasy Ariary | Ar | | 🇲🇰 | MKD | Macedonian Denar | ден | | 🇲🇲 | MMK | Myanmar Kyat | K | | 🇲🇳 | MNT | Mongolian Tugrik | ₮ | | 🇲🇴 | MOP | Macanese Pataca | MOP$ | | 🇲🇺 | MUR | Mauritian Rupee | Rs | | 🇲🇻 | MVR | Maldivian Rufiyaa | Rf | | 🇲🇼 | MWK | Malawian Kwacha | MK | | 🇲🇽 | MXN | Mexican Peso | MX$ | | 🇲🇾 | MYR | Malaysian Ringgit | RM | | 🇲🇿 | MZN | Mozambican Metical | MT | | 🇳🇦 | NAD | Namibian Dollar | N$ | | 🇳🇬 | NGN | Nigerian Naira | ₦ | | 🇳🇮 | NIO | Nicaraguan Cordoba | C$ | | 🇳🇴 | NOK | Norwegian Krone | kr | | 🇳🇵 | NPR | Nepalese Rupee | Rs | | 🇳🇿 | NZD | New Zealand Dollar | NZ$ | | 🇴🇲 | OMR | Omani Rial | ر.ع. | | 🇵🇦 | PAB | Panamanian Balboa | B/. | | 🇵🇪 | PEN | Peruvian Sol | S/ | | 🇵🇬 | PGK | Papua New Guinean Kina | K | | 🇵🇭 | PHP | Philippine Peso | ₱ | | 🇵🇰 | PKR | Pakistani Rupee | Rs | | 🇵🇱 | PLN | Polish Zloty | zł | | 🇵🇾 | PYG | Paraguayan Guarani | ₲ | | 🇶🇦 | QAR | Qatari Riyal | ر.ق | | 🇷🇴 | RON | Romanian Leu | lei | | 🇷🇸 | RSD | Serbian Dinar | din | | 🇷🇺 | RUB | Russian Ruble | ₽ | | 🇷🇼 | RWF | Rwandan Franc | FRw | | 🇸🇦 | SAR | Saudi Riyal | ﷼ | | 🇸🇧 | SBD | Solomon Islands Dollar | SI$ | | 🇸🇨 | SCR | Seychellois Rupee | Rs | | 🇸🇪 | SEK | Swedish Krona | kr | | 🇸🇬 | SGD | Singapore Dollar | S$ | | 🇸🇭 | SHP | St. Helena Pound | £ | | 🇸🇱 | SLE | Sierra Leonean Leone | Le | | 🇸🇴 | SOS | Somali Shilling | Sh | | 🇸🇷 | SRD | Surinamese Dollar | $ | | 🇸🇿 | SZL | Swazi Lilangeni | E | | 🇹🇭 | THB | Thai Baht | ฿ | | 🇹🇯 | TJS | Tajikistani Somoni | SM | | 🇹🇳 | TND | Tunisian Dinar | د.ت | | 🇹🇴 | TOP | Tongan Pa'anga | T$ | | 🇹🇷 | TRY | Turkish Lira | ₺ | | 🇹🇹 | TTD | Trinidad & Tobago Dollar | TT$ | | 🇹🇼 | TWD | New Taiwan Dollar | NT$ | | 🇹🇿 | TZS | Tanzanian Shilling | TSh | | 🇺🇦 | UAH | Ukrainian Hryvnia | ₴ | | 🇺🇬 | UGX | Ugandan Shilling | USh | | 🇺🇸 | USD | US Dollar | $ | | 🇺🇾 | UYU | Uruguayan Peso | $U | | 🇺🇿 | UZS | Uzbekistani Som | so'm | | 🇻🇳 | VND | Vietnamese Dong | ₫ | | 🇻🇺 | VUV | Vanuatu Vatu | VT | | 🇼🇸 | WST | Samoan Tala | WS$ | | 🇨🇲 | XAF | Central African CFA Franc | FCFA | | 🇰🇳 | XCD | East Caribbean Dollar | EC$ | | 🇨🇼 | XCG | Caribbean Guilder | Cg | | 🇸🇳 | XOF | West African CFA Franc | CFA | | 🇵🇫 | XPF | CFP Franc | ₣ | | 🇾🇪 | YER | Yemeni Rial | ﷼ | | 🇿🇦 | ZAR | South African Rand | R | | 🇿🇲 | ZMW | Zambian Kwacha | ZK | The CFA franc zones (XAF, XOF), the East Caribbean dollar (XCD), and the CFP franc (XPF) each serve several countries; the flag shown is the seat of the issuing central bank. Conversions sent through the API in any other currency are rejected at ingest (see the [developer docs](/docs/developers/conversion-currency)). Stripe-driven conversions arrive in whatever currency Stripe charged and are stored as-is in that currency. One transitional alias: amounts arriving in ANG (the retiring Netherlands Antillean guilder) are recorded as XCG, its one-to-one successor. ## How payouts work in multi-currency Earnings accrue **per (affiliate, currency)** in the currency each sale came in, and the run dialog shows you those native balances per currency. **Payouts themselves settle in USD**: when a batch runs, each non-USD commission converts to USD at the most recent published mid-market reference rate available when the payout runs (never more than seven days old), with no Rekomi markup, and the payout records the native amount, the applied rate, and the rate's publication date on every line, so the conversion is fully auditable. This mirrors how creator deals have always settled (quote in any currency, settle in USD at a disclosed rate) and is what makes every balance payable through a single funded run: 1. The affiliate sees exactly what they earned (native amounts stay on every commission and payout line) and exactly what rate applied. 2. One USD run funds everything. There is no per-currency funding, so a EUR or JPY balance never waits for its own batch. 3. The rate is the same mid-market reference used across reporting: the most recent published rate available when the run happens, disclosed with its date on every line, never a spread. A currency with no fresh exchange rate is held out of the run automatically (the dialog tells you) and becomes payable again when rates resume. What arrives on the affiliate's side then follows their payout method as usual: Stripe Express receives USD (if their bank is in another currency, Stripe converts on receipt at Stripe's own rate and conversion charge), bank deposits convert to their local currency at send as part of the disclosed at-cost withdrawal fee, and PayPal receives USD to their balance (any conversion from there is at PayPal's own rate and charge). In the dashboard you'll see the per-currency split on the Payouts page. Your home currency total on the KPI strip aggregates conversions at the reference rate on each conversion's date. ## Creator deals and proposals in multi-currency Deal and proposal amounts can be quoted in any supported currency. The quote is what both sides see everywhere (the proposal page, the emails, and the invoice); settlement is always in USD. The conversion locks at the moment you fund the escrow, using that day's rate, and the invoice records the quoted amount, the USD amount, and the locked rate. The $50 to $50,000 deal bounds apply to the USD value. ## Tax forms in multi-currency U.S. tax law (IRS 1099-NEC) requires reporting in USD regardless of the currency a payment was actually made in. Rekomi's 1099-NEC candidates export works in USD terms: - Non-USD earnings count toward an affiliate's USD-equivalent total using the reference rate on each conversion's date (never today's rate; payments are valued separately, see below). - The candidates export reports year-to-date totals in USD. Rows without a USD reference rate for their date are left out of those totals rather than guessed; the export counts them and names the currencies in a `# NOTE` line at the bottom of the CSV, and the same warning is flagged on the tax page, so the totals can understate until FX coverage is restored. - Payments made during the year count separately, at the USD amount actually paid. Because payouts settle in USD, that is the rate locked on each payout line (see How payouts work above), which can differ from the conversion-date rate used for earnings. - An affiliate appears on the worksheet when either total crosses the $600 candidate threshold, regardless of which currency their conversions came in as. The CSV carries both figures, `year_to_date_cents` (earned, conversion-date rate) and `paid_ytd_cents` (paid, rate locked at payout), so the two can be reconciled. See [Tax forms](/docs/brands/tax-forms) for the full export flow. ## When to change HomeCurrency The honest answer: most brands never need to. USD is a fine reporting currency even for non-U.S. businesses because every comparable benchmark (PartnerStack, Rewardful, your own SaaS metrics) is reported in USD too. Change it when: - You report financials to investors in a non-USD currency. - Your primary cost base is in a non-USD currency and you want the affiliate program P&L to match the rest of the business. - You acquired a brand whose ledger is in another currency and you're consolidating. Otherwise, the per-row native amount is shown everywhere it matters, so a USD home currency does not hide your non-USD revenue from you. ## Limits and edge cases - **Exactly 3 letters, exactly the supported codes above.** No crypto. No legacy codes (e.g. ZWL, VEF). No currency-substitute tokens. - **Minor units follow each currency's real exponent.** JPY, KRW, VND and the other zero-decimal currencies display whole amounts (a 1,000 JPY conversion shows as 1,000 JPY, not 10.00 JPY); the Gulf three-decimal currencies (BHD, JOD, KWD, OMR, TND) keep all three decimals. - **Per-currency unconvertible amount.** If we have a balance in a currency with no usable rate (a rate more than 7 days stale, or, rarely, a code that reached storage before it was on the allowlist at the time of FX refresh), you'll see a warning in the run-payout dialog with the per-currency breakdown, and the same per-currency marker appears under the Reports overview tiles, on Insights (revenue and commission separately), and in the campaign budget card, so nothing is silently swept under the rug. --- # Apply to the curated network Source: https://rekomi.com/docs/brands/network > How brands get vetted into the Rekomi Network for creator-driven recruitment. --- title: Apply to the curated network description: How brands get vetted into the Rekomi Network for creator-driven recruitment. --- The Rekomi Network is a vetted pool of creators on one side, and a vetted pool of subscription brands on the other. Network creators browse the approved campaigns in their dashboard and apply with a pitch. > **Listing works today; creator discovery is launching soon.** Submitting a campaign listing and the human review described here are live now. The creator-side browse surface, where approved campaigns appear to matching creators, is the part still rolling out. In the meantime you can run public application pages and recruit affiliates directly (see [Recruit affiliates](/docs/brands/recruit-affiliates)). Whether a campaign is listed in the network is controlled per campaign, and every listing goes through review before it can surface to creators. Listing also needs two things in place on your account: an active paid plan (a trial does not count yet) and a funding source for paying affiliates, which is either a bank debit on file or bank-transfer funding enabled under Settings, Billing, Payout funding source. While your account is still unverified, bank-transfer funding is the option that counts. Until both are in place the network switch in a campaign's settings cannot be turned on and says why, and new campaigns start private, so you turn the switch on when you want a campaign listed. A campaign that is already listed stays listed and is paused instead (see Pause your network listing below). Affiliates who find you in the network need to know they will be paid. Separate from campaign listings, you can already book individual creators today: browse vetted creators' public pages in `/dashboard/creators` and pay them through escrowed deals. See [Booking creators with deals](/docs/brands/creator-deals). ![The network page showing listing details and review status](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/network-page.png?v=5) ## Apply The listing itself is controlled per campaign: the network visibility setting lives in each campaign's settings. `/dashboard/network` is where you fill in the listing details and watch review status. The form appears once your account has an active paid plan and a funding source; until then the page says what is missing and where to set it up. Fill out: - **Campaign**: pick which of your campaigns you are applying. Each campaign gets a separate network listing if you want. - **ICP description**: who is your ideal customer? Concrete persona, not "everyone". Example: "Solo founders building B2B SaaS, $0-$50k MRR, mostly technical, marketing-curious". - **Target affiliate profile**: who is the creator you want? Example: "Indie hackers with active Twitter or YouTube audiences in the early-stage SaaS space". - **Commission pitch**: in a sentence or two, why is your commission attractive? Example: "30% recurring, no cap, 60-day cookie. Customers pay $99-$499/mo so creators earn $30-$150 MRR per referral". - **Support link**: where creators get help (your support docs, your Slack invite, your email). - **Press link**: your latest coverage. Useful for creator vetting. (Optional but recommended.) - **Notes**: anything else (your internal numbers, unique positioning, restrictions). Submit. Status flows through: `Submitted -> Approved or Rejected`. While submitted, the listing shows as Pending approval. ## What the curator looks at Rekomi curators are humans. They evaluate: - Does the product match the network's positioning (subscription products with clear value)? - Is the commission competitive? - Is the company real (not a scam, not a one-week-old domain, etc.)? - Are the terms fair (no clawback traps, no shadow throttling)? - Is the support story credible? Approvals typically land within 1-3 business days. Rejections come with a written reason; treat one as a request for changes, since revising and saving resubmits for a fresh review. ## What happens after approval Your campaign shows up in `/a/network`, the affiliate-side browse page, where creators filter by category and search. Your target-profile text helps the right creators self-select; it is not an automated matching filter. Network creators see: - Your brand logo and campaign name, description, commission pitch - Real commission terms: rate, whether it's recurring, and the cookie window - Your support link, your press link - An instant-approval badge, when your campaign auto-approves - A trust line showing whether you have a verified payout method and a history of sent payouts - A "Apply to this campaign" CTA When they apply, the application lands in your normal affiliates queue at `/dashboard/affiliates` under the Pending tab. You approve or reject as you would any other application; the network just upstreams the introduction. ## Rejection If your campaign is rejected, you see the reason inline on `/dashboard/network`. Common reasons: - Commission below the typical range for your category - Terms with clawback traps that hurt creators - Product too early-stage to support partner success - Cannot verify your company is legitimate To resubmit, update the listing details and save: saving a rejected listing resets it to `Submitted` for a fresh review. ## Pause your network listing There is no separate withdraw button. To pause a listing, set the campaign's network visibility to Private in that campaign's settings. New creator applications stop immediately. Existing approved affiliates continue earning under your campaign; only the network surfacing pauses. A listing can also pause on its own. If your account loses its funding source or your plan stops being active, the Creator Network page at `/dashboard/network` marks the campaign Paused and says what to restore; the campaign's own network status card says the same once the creator network is open. The campaign stays listed and approved, and its network switch stays on. Creators stop seeing it in `/a/network` while funding or the plan is missing. Once both are back in place the listing shows again on its own, with no new review. ## Network application status `/dashboard/network` lists each submission with its status, and shows the rejection reason inline when a listing is rejected. Edit the listing form and save to resubmit. A listing shown as Withdrawn was taken out of review because the account had no funding source or active plan at the time; listing the campaign again once both are in place submits it for review again. --- # Notifications Source: https://rekomi.com/docs/brands/notifications > Per-user email preferences for the active workspace, and what's always sent. --- title: Notifications description: Per-user email preferences for the active workspace, and what's always sent. --- Each user can pick which emails they receive about workspace activity. Preferences are scoped to a single organization, so the same person can have a different mix per workspace they belong to. Manage your preferences at `/dashboard/settings/notifications`. ## What's user-overridable Each toggle below is on or off per user, per workspace. Defaults are picked so a brand-new admin is notified about the things they'd want to know without being buried. ![The notification settings page with per-event toggles and always-on badges](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/notifications-settings.png?v=4) | Event | Default | Sent to | |---|---|---| | New affiliate application | **On** | Owner, Admin, Manager | | Team member joined | **On** | Owner, Admin | | Broadcast send completed | **Off** | Owner, Admin, Manager | | API key created | **On** | Owner | | Commissions due reminder | **On** | Owner, Admin | | Plan payout-volume nudge | **On** | Owner, Admin | This page only covers emails sent to **you and your team**. The automated emails Rekomi sends to your **affiliates** (payout notices, the welcome email, the commission-earned email, and the rest of the lifecycle catalog) are managed on each campaign's [Emails tab](/docs/brands/emails), not here. ## What's always sent (transactional) These emails are tied to the legal, financial, or operational state of your account and cannot be disabled. They show in the settings UI with an "Always on" badge so you know what to expect in your inbox. | Event | Sent to | |---|---| | Trial started | Billing contact | | Trial ending soon | Billing contact | | Trial expired | Billing contact | | Payment receipt | Billing contact | | Payment failed | Billing contact | | Plan changed | Billing contact | | Subscription cancelled | Billing contact | | Migration import failed | Owner, Admin | | Payout failed | Billing contact | Billing and subscription notices (trials, payments, plan changes, cancellation) go to the workspace **billing contact**, independent of which users are members. Payout-failed alerts also go to the billing contact. Migration import failures fan out to the workspace **Owners and Admins**, since they're the ones who act on them. ## How it works under the hood - Your choices are stored per user, per workspace, per event. If you have never touched a toggle, the event's default applies. - For each event, Rekomi finds the workspace members whose role is eligible, checks each person's preference, then sends. - Always-on events skip preference checks entirely; they cannot be switched off. ## The notification bell Email is not the only channel. The bell in the dashboard top bar shows a badge with your unread count and opens a list of your most recent in-app notifications (it checks for new ones about once a minute). Clicking a notification takes you to the related page and marks it read; **Mark all read** clears the badge in one go. ## Adding new notifications When a new brand-facing email event is added to the catalog it appears in this list automatically; every entry renders as either a toggle or an "Always on" badge in the settings UI. The catalog covers emails to you and your team; affiliate-facing automated emails are catalogued separately on the [Emails page](/docs/brands/emails). --- # Rekomi for online stores Source: https://rekomi.com/docs/brands/online-stores > The store owner's guide to Rekomi on Shopify, Wix, BigCommerce and WooCommerce. Install in minutes, what gets tracked, discount codes, product rates, refunds, store credit rewards, payouts and tax. --- title: Rekomi for online stores description: The store owner's guide to Rekomi on Shopify, Wix, BigCommerce and WooCommerce. Install in minutes, what gets tracked, discount codes, product rates, refunds, store credit rewards, payouts and tax. --- Rekomi runs the affiliate program for your store. Install the app for your platform, create a campaign with the commission you want to pay, and every referred order is tracked, every refund is handled, and every affiliate is paid and tax-reported by Rekomi. Nothing to paste, no theme edits, no spreadsheets. Shopify, Wix and BigCommerce each have a Rekomi app in their app store; WooCommerce has the Rekomi WordPress plugin. Install, pick a plan (every plan starts with a 14-day free trial, billed through your platform), create a campaign, and tracking is live. Affiliates get a referral link, a personal discount code and product links; you see every referred order; Rekomi pays the affiliates in 130+ countries and files the tax forms as payer of record. ## Install in minutes - **Shopify**: install Rekomi Affiliate Marketing from the App Store. The app installs its own web pixel and reads each order through Shopify's order webhook, so links track even when the buyer blocks trackers. Guide: [Install on Shopify](/docs/brands/install/shopify). - **Wix**: add Rekomi Affiliate Marketing from the App Market. The app injects click tracking and records each paid store order server-side. Guide: [Install on Wix](/docs/brands/install/wix). - **BigCommerce**: install Rekomi Affiliate Marketing from the App Marketplace. The app registers your order webhooks and the storefront script for you, on every storefront of a multi-storefront store. Guide: [Install on BigCommerce](/docs/brands/install/bigcommerce). - **WooCommerce**: install the Rekomi plugin and click Connect in wp-admin; orders post server-to-server on every payment method. Guide: [Install on WooCommerce](/docs/brands/install/woocommerce). On Shopify, Wix and BigCommerce the whole program runs inside your store admin: campaigns, the signup page designer, affiliates, referred sales, coupon codes, product links, payouts and billing. The same account also works at [app.rekomi.com](https://app.rekomi.com) if you prefer the full dashboard. ## What gets tracked - **Referral links.** Each affiliate gets a link to your store (or to any product) with their code attached. Clicks are recorded first-party on your storefront. - **Discount codes.** Each affiliate can get a personal code, created as a real discount on your platform (a Shopify discount, a Wix coupon, a BigCommerce promotion, a WooCommerce coupon). A redeemed code credits the affiliate even when there was no tracked click, which is how most social and video traffic converts. See [Coupons](/docs/brands/coupons). - **Orders.** A paid order is a sale. Commission is computed on the order's goods total before tax and shipping on Wix, BigCommerce and WooCommerce; on Shopify the default basis is the order total, and per-product rates pay on the goods line. Offline payment methods count once you mark the order paid. - **Products.** Your catalog syncs into Rekomi so affiliates can link to specific products and you can set per-product commission rates (a higher rate on the products you want pushed, zero on the ones you do not). See [Product commissions](/docs/brands/product-commissions). - **Customers into affiliates.** The Grow page lists your top customers by lifetime spend so you can invite them to become affiliates in one click. ## Refunds, cancellations and chargebacks - **A refund reverses the commission.** Full or partial, the refunded share of the commission comes back automatically, and the sale only flips to Refunded once refunds cover the whole order. Rekomi reads your platform's own refund events (Shopify refunds/create, Wix refunds, BigCommerce refund webhooks, WooCommerce refund posts), so nothing needs forwarding. - **A refund after the affiliate was already paid.** Since October 2026 the refunded share of the paid commission is recovered from that affiliate's next payout automatically. Your Payouts page shows what is owed back under "Owed back from refunds" with the option to write it off; the affiliate sees the same figure on their side. If you would rather pull the money back right away, the paid line can still be reversed on Stripe from the payout's page, and the two never double-count. See [Payouts](/docs/brands/payouts#refunds-after-a-payout). - **Cancellations.** An order cancelled before payment never becomes a sale. An order cancelled after payment is refunded by your platform, and that refund reverses the commission like any other. - **Chargebacks.** A lost dispute on your payment processor is not sent to Rekomi as a refund event. Refund the order in your store (so the refund webhook fires), or mark the sale invalid from the Sales page. - **Order edits.** An edit that adds to a paid order does not change the commission already recorded; an edit that removes items is a refund in your platform and reverses its share. - **The refund hold.** Commissions become payable only after your refund-hold window (30 days by default, adjustable from Instant to 90 days under Settings, Payouts), which is why most refunds land before any money moves. ## Store credit instead of cash Many stores would rather reward affiliates and referring customers with store credit. On Shopify, Wix and BigCommerce a campaign's reward can be **store credit** instead of a cash commission: the affiliate earns the commission rate you set (or a fixed amount per sale), and Rekomi adds it as store credit on Shopify or BigCommerce, or sends a Wix gift card for the amount. Nobody needs a payout method or a tax form, and a refund takes the credit back. Set it on the campaign under Reward. The details, including the one-time permission approval on Shopify, are in [Free month and store credit rewards](/docs/brands/free-month-rewards#store-credit-for-shopify-wix-and-bigcommerce). ## Payouts and tax For cash commissions, Rekomi pays your affiliates for you. You fund a payout run (or turn on automatic payouts), Rekomi disburses the net to each affiliate in 130+ countries through Stripe Express, bank deposit or PayPal, and charges a flat 3% of the commissions paid. Rekomi (Uplup Inc.) is the payer of record: it collects the W-9 and W-8 forms and files the 1099-NECs, so you never collect a tax form or file one for an affiliate. See [Payouts](/docs/brands/payouts) and [Tax forms](/docs/brands/tax-forms). ## Good to know - **Markets and multiple currencies.** Orders are recorded in the currency they were paid in; payouts settle in USD at the day's rate, and your dashboard shows each figure in your home currency. See [Multi-currency](/docs/brands/multi-currency). - **POS and draft orders.** A paid order is a paid order: a point-of-sale or draft order credits an affiliate only when it carries their discount code or referral, which is how a real referral reaches it. - **Shopify Collabs** can run side by side with Rekomi on the same store. Rekomi adds cross-platform affiliate accounts, tiers, sub-affiliate recruiting, store credit rewards and the creator network on top. - **Billing.** Your Rekomi plan is billed through your platform (Shopify Billing, Wix, BigCommerce), on the same invoice as your other apps. WooCommerce stores bill through Rekomi directly. - **Moving from another app?** The importer under Settings, Import takes affiliates and sales history from 21 sources, including Shopify Collabs, UpPromote, GoAffPro and Refersion. See [Migrations](/docs/brands/migrations). --- # Payouts Source: https://rekomi.com/docs/brands/payouts > Fees, timing, methods, batches, minimums, and failure handling. --- title: Payouts description: Fees, timing, methods, batches, minimums, and failure handling. --- Rekomi runs affiliate payouts for you, globally. You fund a payout run and Rekomi disburses the net to each affiliate, handles the cross-border settlement, and keeps the tax reporting in sync. You never cut checks, wire money affiliate by affiliate, collect their payout tax forms, or file their 1099-NECs: Rekomi (Uplup Inc.) is the payer of record, so the year-end forms go out through Stripe under Rekomi's name and the candidates worksheet is for your records. One flat 3% platform fee, all-in. New accounts fund payouts by bank transfer from any country; once Rekomi verifies your business you can also link a US, EU, UK, or Canadian bank debit. Affiliates get paid in 130+ countries. Money always clears before anyone is paid. ## Fees and timing Everything a payout costs, and how long each step takes, in one place. | Fee | Who pays it | Amount | | --- | --- | --- | | Platform fee | You (the brand) | 3% of the payout | | Withdrawal fee, paid via Stripe Express | The affiliate | $2.25 per month + 0.25% of the payout | | Withdrawal fee, paid via PayPal | The affiliate | 2% of the amount sent, capped at $20 | Notes on the fees: - The platform fee is **all-in**: Stripe's payment-processing cut is already baked into it, so funding by US bank debit or bank transfer costs exactly 3% on what you pay your affiliates, and nothing more. The one exception is the international debit line in the next note. - The platform fee is charged **on top of** the gross commissions, never taken out of the affiliate's payout. - Funding by an international bank debit (EU, UK, or Canadian bank) adds one labeled line to your receipt: **international funding cost, at cost**. This is not a Rekomi fee: it is Stripe's own currency-conversion charge passed through with no markup, currently about 1% of the charge, and Rekomi's 3% stays 3%. You can avoid it entirely by funding by bank transfer instead, which virtually every country can use. - The affiliate withdrawal fee is a conventional pass-through cost that Rekomi takes no margin on. The flat $2.25 is charged just once per calendar month no matter how many payouts an affiliate receives; for affiliates paid via PayPal the withdrawal fee is 2% of the amount sent, capped at $20 (exactly PayPal's own sending cost), and PayPal deducts nothing on arrival; a currency conversion on the affiliate's Stripe or PayPal side, if any, is at that provider's own rate. See [the affiliate-side fee breakdown](/docs/affiliates/payouts#withdrawal-fee) for a worked example. | Step | Typical time | | --- | --- | | Funding clears (US bank debit) | About 4 business days | | Funding clears (EU / UK / Canadian bank debit) | About 6 / 3 / 5 business days | | Funding available (bank-transfer balance) | As soon as your transfer arrives | | Stripe Express settlement to the affiliate | 1 to 3 business days (U.S.), 3 to 7 (international) | | PayPal delivery to the affiliate | Usually within minutes of dispatch | PayPal payouts land in the affiliate's PayPal balance, and they withdraw to their bank through whatever options PayPal offers in their country. ## Funding your payouts Every payout run is funded by you first: Rekomi collects the gross commissions plus the platform fee, waits for the money to clear, and only then pays your affiliates. That order is deliberate. It means Rekomi never fronts money on your behalf, and your affiliates are never paid with funds that could bounce. Your business does not need to be in the US, and you never need a Stripe account of your own. Every new account funds by bank transfer, which works from any bank in almost any country with no extra fee. Your first payout run is funded by bank transfer whether or not you are verified. Once your account is verified and that first run has settled, the bank-debit options below open up as well, and you can set and forget them. The full country-by-country rail list is in [Funding countries](/docs/brands/funding-countries). Here are all five funding options side by side. Bank transfer is available from day one and funds every account's first payout run; the four bank debits open once your account is verified and that first run has settled. Both modes are set up once under Settings › Billing › Funding: | | Option | Works from | Charged in | Fee | Money clears | | --- | --- | --- | --- | --- | --- | | | US bank debit (ACH), once verified | US banks | USD | 3% platform fee, nothing else | ~4 business days | | | EU bank debit (SEPA), once verified | Any SEPA-zone bank | EUR, at the day's mid-market rate | 3% platform fee + ~1% at-cost currency conversion | ~6 business days | | | UK bank debit (Bacs), once verified | UK banks | GBP, at the day's mid-market rate | 3% platform fee + ~1% at-cost currency conversion | ~3 business days | | | Canadian bank debit (PAD), once verified | Canadian banks | CAD, at the day's mid-market rate | 3% platform fee + ~1% at-cost currency conversion | ~5 business days | | | **Bank transfer** | **[Virtually any country](/docs/brands/funding-countries)** (sanctioned countries excepted) | USD, pushed by you (SWIFT, Wise, or domestic transfer) | 3% platform fee, nothing else | The moment your transfer arrives | The 3% is the same [platform fee](#fees-and-timing) every option pays, never a location surcharge. The ~1% on the international debits is Stripe's own conversion cost passed through at cost, itemized on your receipt with no markup; bank transfer never incurs it. ### Bank debit (set-and-forget, once verified) Once your account is verified, link a bank account in your region and authorize a debit mandate through Stripe's secure flow. It is used from the run after your first one: the first funded run of every account is a bank transfer, so the money is already in before anything is paid out. From then on, every payout run (manual or scheduled) charges that account automatically; you never move money by hand. A bank debit linked before verification stays on file and is used once you are verified and your first run has settled. Linking takes about a minute. On the US and Canadian flows, if your bank doesn't appear in the instant-login list you can choose **enter bank details manually**; a small verification deposit arrives within a business day or two, you confirm the code from it, and the mandate activates on its own. SEPA and Bacs authorize the mandate directly in the linking flow, with no deposit step. Two notes on the debit rails: - Bank debits are a **verified-account** feature, ACH included. Until Rekomi has verified your business (see [Verification and payout reviews](#verification-and-payout-reviews) below), you fund payouts by bank transfer; once verified, link any debit rail here and every run charges it automatically. - On the international rails, your receipt shows the exact foreign-currency amount debited ("Debited from your bank as €X") plus the at-cost international funding line, so your bank statement reconciles to the cent. ### Bank transfer (every new account, from almost any country) This is how every new account funds payouts, and it stays available after verification. No mandate at all: Rekomi gives you a dedicated USD account number, you push money to it whenever you like (domestic transfer, SWIFT wire, or a service like Wise), and payout runs draw from that balance the moment the money arrives. Your current balance and the transfer details live on the funding settings page. Owners and Admins see the account numbers there; Managers see the balance only. There is no per-run bank fee and no international funding cost line: a brand in Türkiye, Brazil, India, Nigeria, Australia, or almost anywhere else pays the same flat 3% as a US brand. Until your account is verified, funded runs are capped at $2,500 per 30 days and dispatch 7 days after the money lands (see below); verified accounts have no cap and no wait. The only countries that cannot fund are internationally sanctioned ones; the exact list, and the rails available per country, are in [Funding countries](/docs/brands/funding-countries). If a run needs more than your balance holds, it stops cleanly and tells you the exact shortfall; nothing is charged and nothing moves until you top up and run it again. Note the two sides are separate: this section is about where YOUR business can be. Where your **affiliates** can be paid is its own list, 130+ countries across Stripe Express, direct bank deposits, and PayPal; see [Payout countries](/docs/affiliates/supported-countries). ### Which should you pick? | | You are | Fund with | | --- | --- | --- | | | A US brand with a US bank | **Bank transfer** until verified, then **US bank debit (ACH)** if you prefer: link once and every run charges automatically. | | | An EU, UK, or Canadian brand | **Bank transfer** until verified, then **your local debit rail** (SEPA, Bacs, or PAD) for set-and-forget scheduled payouts, or keep pushing funds on your own schedule. | | | Anywhere else in the world | **Bank transfer.** Push USD from any bank (SWIFT or Wise included): no mandate and no international funding cost. | Whichever you choose, every funded run produces a numbered RKM receipt with the full breakdown: gross commissions, platform fee, any at-cost international line, and the net each affiliate received. On US ACH and SEPA debits that RKM number also appears as the description on your bank statement; Bacs and Canadian PAD statements show the debit mandate reference instead, and bank-transfer funding shows whatever reference your own bank attached to the transfer, so the receipt is always the reconciliation anchor. ## Verification and payout reviews Rekomi moves real money on your behalf, so it follows the same rules every payout platform follows: the platform never fronts money, new accounts earn trust, payees are known before money lands, and anything that looks wrong is checked by a person before it goes out. In practice that means two account states and one review step. **Unverified (every new account).** Three rules apply until Rekomi verifies your business: - **Prefund by bank transfer.** Payout runs are funded from your bank-transfer balance. Bank debits (ACH, SEPA, Bacs, PAD) are not offered yet. - **A 7-day holding period.** Affiliates are paid 7 days after your funding lands. The payouts page shows the date for each run. - **A $2,500 cap per 30 days** on funded runs. The funding settings card shows how much of the window is left. Everything else works from minute one: campaigns, tracking, affiliates, commissions and accrual. Most brands reach their first payout run weeks after sign-up, so there is time to get verified before it matters. **Getting verified.** Open Settings › Billing › Payout funding source. The Get verified block has three lines, each with a tick and one button for whatever is missing: 1. **Your website.** We confirm you control the domain you signed up with: the Rekomi tracking snippet on it is enough, and so is a verified custom Rekomi domain under it. Otherwise add one DNS TXT record (the card shows the exact name and value) and press Confirm website. 2. **A business email on that domain.** If your login is already on that domain, this is done. Otherwise type an address ending in your domain and click the link we send. If your business has no mailbox on its domain you can skip this line, and a person always decides. 3. **One strong proof.** Any one of: a connected sales platform with real history (Stripe, Shopify, Paddle, Chargebee, Lemon Squeezy, Polar, Braintree, PayPal, Wix, BigCommerce or WooCommerce), a bank account in your business's name (a verify-only link, nothing is debited and nothing is saved), or a one-time ID check. Then press Submit for review. A person at Rekomi looks at it, usually within one business day, and emails you either way. Sometimes we ask for one more thing first; the card shows the question and a reply box. Verified accounts get every bank debit rail and instant dispatch: affiliates are paid the moment your funding settles, with no holding period and no cap. Two things apply to every account, verified or not: the first funded payout run is a bank transfer, and a person at Rekomi takes one look at the account before that first run goes out. From the run after it, a verified account's bank debit takes over and nothing waits. If our automated checks flag an account as high risk, funding and payouts wait until verification is complete. Verification is the same few-minute flow under Settings, Billing, Funding. **No payouts on a trial.** A brand-funded payout run needs an active paid subscription. During a trial, commissions keep accruing and the payouts page says "Payouts start once your subscription is active"; activate your plan, then get verified; your first run is funded by bank transfer and every run after it is instant (until verification the unverified rules above apply). Comped accounts and app-store-billed accounts (Shopify, Wix) pass through their own billing status. **The first-funding look.** Before a new account's first payout run goes out, a person at Rekomi takes one look at the account, whatever the amount. That happens once, verified or not. For large brands it knows well, Rekomi can grant a higher tier (super verification) that lifts the first-run bank transfer and the look; it is granted by Rekomi, never requested. **The review step.** A small number of runs are held for a quick check before they go out: a run where the brand and a paid affiliate share a device or sign-in address, where the funding bank's holder name matches nothing on the account, where hand-typed sales are paid to an affiliate who joined the same day, or a large first run on an unverified account. A held run has charged nothing and paid nobody. You see "This payout run is being reviewed before it goes out" on the payouts page, we review it within a business day, and we email you when it is on its way or if we could not send it. Nothing is ever approved automatically. ## Where to manage payouts `/dashboard/payouts` shows what you owe, what is settling, and what has been delivered, with three tabs: **Being paid out**, **Paid**, and **Failed**. ![The Payouts page with owed, being paid out, paid, and failed summary cards above the payout table](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/payouts-overview.png?v=5) - **Being paid out**: payouts you have created that are funding and settling (status Pending or Processing). - **Paid**: delivered payouts with affiliate, amount, method, and reference; expand a row to see the settled date. - **Failed**: payouts that hit an error (closed Stripe Express account, KYC hold, dispute). Each shows `failureReason` for triage. Payout runs are funded by the funding source you set up once from billing settings (see [Funding your payouts](#funding-your-payouts): a bank debit in your region, or a push-by-bank-transfer balance that works from any country). Every funded run produces a numbered RKM receipt available from the Receipts button. Until a usable funding source is in place, the Payouts page shows a banner that says which step applies to your account. ## Methods and countries Affiliates get paid through Rekomi's built-in payout methods: | Method | Countries | How it works | | --- | --- | --- | | **Stripe Express** | 42, including the US | Affiliates onboard once with Stripe Express and funds settle directly to their bank account. | | **Bank deposit** | 60 more | Direct local-currency deposits via Stripe: affiliates add their bank details once on a Stripe-hosted form (plus a W-8) and are paid through the same runs. | | **PayPal** | 31 more, for 130+ combined | Covers the countries the two Stripe methods do not reach. Affiliates add their PayPal email (and, outside the US, a W-8) and are paid through the same runs. | You do not pick a per-affiliate payout method. Each affiliate is paid through whichever method covers their country; the full searchable list, with each country's method and fee, is in [Payout countries](/docs/affiliates/supported-countries). Only account Owners and Admins can run, retry, or reverse a payout, and only from a signed-in dashboard session (never via an API key). ## Running a payout batch Payouts are consolidated through Rekomi: you fund one payout run and Rekomi pays every affiliate through its built-in payout methods. There is nothing to reconcile by hand and no separate system to maintain. (The old manual "mark as paid" option has been removed from the payout screens; consolidated payouts through Rekomi are the way to pay affiliates.) 1. Open **Payouts** in the dashboard sidebar. 2. Click **Run payout**. The dialog previews every affiliate with an eligible balance (approved commissions past your grace window, and a ready payout account) and the amount owed, with per-currency balances shown in their native amounts (non-USD balances convert to USD at the mid-market rate when the run executes, recorded on each payout). It also lists affiliates with an eligible balance who cannot be paid yet (for example, payout account not connected), so you know who to nudge. 3. Select the affiliates you are paying in this batch. 4. Confirm. Rekomi charges you the gross commissions plus the platform fee (see [Fees and timing](#fees-and-timing)), deducts the affiliate's withdrawal fee from the gross, disburses the net to each affiliate through their payout method, and records the payout so balances, history, and tax totals stay correct. ![The run-payout dialog with candidates selected and the gross, platform fee, and affiliates-receive breakdown](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/run-payout-dialog.png?v=5) Each payout starts in the Being paid out tab, then settles through the payout method. You can run batches as often as you want. ## Minimum payouts Two minimums keep tiny balances from triggering payouts before you want them to, and they gate different things: - **Campaign minimum**: set on each campaign, this is an eligibility floor. While an affiliate's balance from that campaign is under the floor, those commissions are not payable in any run, manual or automatic; they keep accruing until the balance crosses it. - **Account minimum**: set in payout settings, this applies to automatic runs only. On a scheduled run, affiliates whose combined balance is under it roll forward to the next run. A manual batch can pay any eligible balance below the account minimum; the campaign floor still applies. Both minimums are USD amounts. An affiliate's earnings in other currencies count toward them at their USD value on the run date (a 2,500 threshold means $25.00, whatever currencies the balance is made of). The top of `/dashboard/payouts` shows a **payout terms card** that states your terms in plain sentences: your schedule (or "On demand"), when commissions become payable after the sale, your account minimum, and the next automatic run date. Your affiliates read the same terms, worded for them, in their own portal, so both sides see the same rules. ## Included payout volume Each plan includes a monthly partner-payout volume to help you pick the right tier: Starter $2,500/mo, Growth $15,000/mo, and Pro and Enterprise unlimited. It is measured per calendar month across all your campaigns. This is a soft guide, not a hard cap. You are never blocked from paying your affiliates, and there is no overage fee: the flat platform fee applies to all payout volume regardless of plan. If you go over your plan's included volume, Rekomi simply suggests upgrading for more headroom; nothing about your payouts changes. Your billing page shows your payouts so far this month against your plan's included volume. ## Payout statuses | Status | Meaning | | --- | --- | | Pending | Created, not yet executed | | Processing | Transfer initiated, waiting for settlement | | Paid | Settled | | Failed | Error during execution; triage in the Failed tab | In the dashboard, Pending and Processing both display as Being paid out. ## Schedules Payouts can run on demand or on a schedule. In `/dashboard/settings/payouts` you choose On-demand (you run each batch yourself) or Automatic, and for Automatic you set a Daily, Weekly, or Monthly cadence (weekly supports multiple days), plus the time of day it fires, in 30-minute steps. Scheduled runs fire within half an hour of your chosen time. The time is read in your account timezone, the same one set in Settings › Profile; changing it in either place changes it everywhere, so times across your account (report ranges, conversion timestamps, and this schedule) all stay consistent. The [per-affiliate minimum](#minimum-payouts) applies to automatic runs. You can still run a payout batch any time from the Payouts page. ![The payout schedule settings with on-demand vs automatic modes and the grace window selector](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/payout-schedule.png?v=5) ## Refund hold and approval timing By default, a sale's commission stays inside the grace window for 30 days after the sale. This covers the standard refund window. Once a commission is older than your grace window it automatically becomes available for payout; there is no manual step and nothing to wait for. Think of it as your net terms (net-30, net-60, net-90) for absorbing refunds before an affiliate gets paid. Set the grace window to Instant, 7, 14, 30, 60, or 90 days under Settings, Payout schedule, Refund hold. Every plan starts at 30 days and can set any of those; the hold is a safety setting, not a plan feature. Shorter windows pay affiliates sooner but increase clawback exposure. The window is one number for the whole account, and a manual payout run respects it automatically: the run pays each affiliate's commissions that are already past the hold and leaves the younger ones in grace for a later run, so you never have to exclude them by hand. ## Failed payouts When a Stripe transfer fails, Rekomi: 1. Marks the payout Failed and records a `failureReason` (shown in the Failed tab and returned by the payouts API). 2. Emails your workspace billing contact that the payout failed, with the reason. 3. Leaves the underlying sales in `Approved`, so you can retry once the affiliate fixes their Stripe Express account. Retry from the Failed tab. Click the failed payout, see the reason, contact the affiliate if needed, then click "Retry". One exception: when the failure was the funding charge itself (your bank debit bounced or the charge was refused), the payout cannot be retried on its own, because no money ever came in. The sales go back to Owed to affiliates, the Failed row stays as the record and says so, and your next payout run (Run payouts at the top, or the automatic run) picks the sales up once your funding source is topped up. ## Per-affiliate payouts When an affiliate has money due (approved commissions past your grace window), a **Pay out** button appears in their detail page header showing the amount, and a **Pay** action appears on their row in the affiliates table. Either one takes you to the Payouts page with the run dialog already open and that affiliate pre-selected, so paying one person is two clicks. For finer control, click any affiliate in `/dashboard/affiliates` and open the **Payouts** tab to manage that one affiliate end to end: - A summary of what they are owed: **due now** vs still inside the grace window, plus what's already **paid** and their gross/net revenue, broken out per currency. Net is the amount that reaches the affiliate: the gross commission minus the affiliate's withdrawal fee. The platform fee is charged to you on top of the gross, never taken out of the affiliate's payout (see [Fees and timing](#fees-and-timing)). - **Due / Pending / Paid / Refunded** tabs listing every commission, with the customer and sale behind each. - On the **Due** tab, tick individual commissions (or **select all on the page** with the header checkbox) and click **Pay $X from N referrals** to pay exactly that subset, or click **Pay all due** to pay their entire eligible balance in one shot, regardless of what's selected or which page you're on. - The confirm dialog shows the net that will reach the affiliate through their payout method and asks you to type the amount to confirm. - Below the manager, their full **payout history**: every batch, how many commissions it covered, the status, and any failure reason, with one-click **Retry** on failed payouts. Only commissions past your grace window are payable; in-grace commissions show under **Pending** and become payable automatically once the window passes. A commission can only ever be paid once, even if two people click at the same time. ## Refunds after a payout Sometimes a sale is refunded after the commission on it was already paid out, most often because the refund landed after the grace window. Since October 2026 Rekomi handles that for you: the refunded share of the paid commission becomes an amount **owed back**, and the affiliate's next payout deducts it automatically. In detail: - When the refund arrives, your workspace admins get a notification naming the paid commission, the amount now owed back and the payout it was paid on. Nothing moves yet. - The Payouts page lists every open amount under **Owed back from refunds**, per affiliate, with the sale and the original payout linked. The affiliate sees the same figure on their own payouts page with a plain explanation. - The next time that affiliate is paid (a run you start, or an automatic one), the owed amount comes off the top of their payable commissions. The run dialog shows the deduction on their row before you confirm, the payout's page shows "recovered from earlier refunds", and the commissions listed on it are still the full amounts earned in that run. The platform fee is charged on what is actually paid. - If the owed amount is larger than what the affiliate has earned since, nothing is paid out to them yet and the rest keeps accruing; the balance is deducted as soon as it covers the amount owed (and the withdrawal fee). - Partial refunds net their share only (a 30% refund recovers 30% of that commission), and several refunds on the same sale never recover more than the commission that was paid. - You can **write off** what is left of any open amount from the Owed back table; nothing more is deducted after that. - The 1099 records stay right by construction: a recovery inside the year it was paid nets that year's figure; a recovery in a later year leaves the earlier year's payment as it was. The paid line can also be **reversed on Stripe** right away, as before; a Stripe reversal shrinks the owed amount by the same figure, so the two never recover the same refund twice. Open the payout from the Paid tab (click **Detail**) and choose one of two actions: - **Reverse selected commissions**: tick the commissions tied to the refunded sales and reverse just those. The dialog lists each selected commission with an amount box that starts at what is left on it; leave it for a full refund, or lower it to the refunded share when the customer got a partial refund (a 30% refund claws back 30% of that commission). The affiliate's net share of those amounts is pulled back from their Stripe Express account, and your funding for them (those amounts plus the platform fee on them) is refunded to the bank that funded the run. Every other commission on the payout stays paid. A partly reversed line shows the amount reversed so far next to its total, and you can come back and reverse the rest later (it is then reversed on its own, not mixed with other lines). Lines reversed in full are marked and cannot be selected again, and you can reverse up to 50 commissions per request. If your selection covers the whole payout, use the full reversal instead. - **Reverse entire payout**: claws back every commission on the payout in one action, after a confirm step that spells that out. Rekomi reclaims the full net from the affiliate's Stripe Express account and refunds your share of that payout's funding charge (the gross commission plus the platform fee for that one payout) to the bank that funded the run. On an international rail the refund is issued in the currency you were debited in (EUR, GBP, or CAD), prorated at the same exchange rate as the original charge, so it reconciles against your statement. The at-cost international line is not refunded, because Stripe does not return its own conversion charge; the same is true of the ACH pass-through on free-for-life accounts. Once any commission on a payout has been reversed, in full or in part, the whole-payout action is no longer offered for it; reverse the remaining commissions individually instead. The receipt keeps the full audit trail either way: reversed commissions are marked on it, each fully reversed payout is marked, and once every payout on a run is reversed the receipt itself shows as reversed, with the credit detail in your fees ledger. Both actions apply to Stripe Express payouts funded through Rekomi. Shopify-billed workspaces reverse a payout as a whole (the platform fee is credited per payout), and payouts that predate USD-settled runs also need a full reversal. A PayPal payout can only be recovered while the payment is still unclaimed by the affiliate (support cancels the unclaimed item and the funds return to your float); once a PayPal or bank deposit payout has reached the affiliate there is no immediate clawback, so for those rails the amount owed back is recovered from the affiliate's next payout as described above. Either action requires an Owner or Admin in a signed-in dashboard session; neither is ever available to API keys. They are not yet available in the WordPress plugin's admin screen, so use the web dashboard. ## Funding questions **Can I fund payouts with a card?** No. Card payments carry chargeback exposure that does not mix with irreversible affiliate payouts, so funding is bank-only by design: a bank debit in your region or a bank transfer from anywhere. **I'm not in the US, EU, UK, or Canada. How do I pay affiliates?** Bank-transfer funding, the same way every new account starts. Rekomi issues you a dedicated USD account you can reach from any bank worldwide, by SWIFT or through a service like Wise, and runs draw from that balance. There is no international funding cost line on it, and no cap once your account is verified. Country-by-country rail availability is in [Funding countries](/docs/brands/funding-countries). **Why was my run capped?** Until Rekomi verifies your account, funded payout runs and deals together are capped at $2,500 per rolling 30 days, on every funding rail. The run dialog tells you how much of the window is left before you confirm. Get verified (Settings, Billing, Payout funding source) to remove the cap; a person looks, usually within one business day. **My funding bank changed.** Open Settings › Billing › Funding and click Update to link the new account; the new mandate replaces the old one. If your bank revokes the debit mandate, Rekomi disconnects the source, pauses automatic payouts, and emails you so you can re-link. **Why is my payout funding paused?** A bank dispute on a funding charge pauses payout funding while we review it. Automatic payouts are turned off and runs will not start. Contact support@rekomi.com and we will review and restore it; commissions keep accruing in the meantime. ## Tax forms A tax form is required before an affiliate's **first** payout on every rail: a W-9 for U.S. affiliates, a W-8BEN or W-8BEN-E for everyone else. An affiliate without one shows as "Tax form missing" in the payout dialog and is skipped until they upload it from their settings; nothing is withheld, the balance simply waits. Separately, before paying U.S. affiliates at or above the IRS reporting threshold in a calendar year ($2,000 for 2026 payments), that W-9 needs to be on file for the 1099. Affiliates paid through Stripe provide their tax details during Stripe Express onboarding (Stripe collects and holds the W-9); affiliates on PayPal or bank deposit upload a form to Rekomi. The Affiliates near the US tax threshold view on the Payouts page shows who is approaching the threshold, counted on payments made during the year rather than commissions earned. At year end the 1099-NECs go out through Stripe with Rekomi as the payer of record; the candidates worksheet is for your records. See [Tax forms](/docs/brands/tax-forms) for the full flow. --- # Plans and trials Source: https://rekomi.com/docs/brands/plans-and-trials > Four plan tiers, 14-day free trial on every plan, and what gets gated where. --- title: Plans and trials description: Four plan tiers, 14-day free trial on every plan, and what gets gated where. --- Rekomi is paid-only with a 14-day free trial on every plan. There is no free tier. At signup you pick a plan, enter your card, and your trial starts. You won't be charged until the trial ends. The free trial is once per person. If you already used it in another workspace you own, a new workspace starts on a paid plan: you pick a plan, enter your card, and billing begins right away. The plan picker says so when that applies. ## The four tiers | Tier | Price | Who it is for | |---|---|---| | **Starter** | $29/mo (or $290/yr) | Solo founders and small teams running a single campaign. Includes up to $2,500/mo in partner payouts. | | **Growth** | $49/mo (or $490/yr) | Growing SaaS companies that need AI features and white-label embed. Includes up to $15,000/mo in partner payouts. | | **Pro** | $99/mo (or $990/yr) | Established subscription businesses with the [white-label affiliate experience](/docs/brands/white-label), a custom sending domain, and unlimited partner payouts. | | **Enterprise** | Custom | Custom limits, a reduced take rate, premium support. | Every plan also carries a flat 3% on affiliate payouts, which covers Rekomi paying your affiliates worldwide and handling their tax forms. Annual billing saves 17%. [The pricing page](https://rekomi.com/pricing) is always the source of truth if these numbers ever drift. ## The 14-day trial When you sign up, you create your account, land on `/sign-up/plan` to pick a tier (Starter, Growth, or Pro), and complete Stripe Checkout. A card on file is required. Stripe holds your card without charging it for the first 14 days. Stores that install the Rekomi app for Shopify follow the same paid-only, 14-day-trial model but bill through Shopify instead of Stripe: the app opens on its Subscription page, you pick a plan and approve the charge on Shopify's billing screen, no card is entered in Rekomi, and the charge lands on your Shopify invoice when the trial ends. To cancel, use the "Cancel your free trial" or "Cancel your plan" link below the plan cards on the app's Subscription page: a mid-trial cancel never charges you, and cancelling a paid plan stops Shopify billing right away. Uninstalling the app also cancels the subscription. Stores that install the Rekomi app for Wix bill through Wix the same way: pick a plan on the app's Subscription page and the charge lands on your Wix invoice when the trial ends. Two Wix billing rules to know: the free trial is available once per Wix account, and switching plans ends the trial immediately (Wix billing policy). Unlike Shopify, uninstalling the Wix app does not cancel the subscription; cancel from the Wix subscriptions page in your Wix account. ![The plan picker at sign-up with the 14 day trial](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/sign-up-plan.png?v=5) During the trial you get the features of the tier you picked, exactly as if you were already paying for it. The trial is a **payment status, not a tier override**: it does not unlock features from higher tiers, and the plan gates below apply normally throughout the trial. Two trial-specific details: - **S2S tracking** (including the lead and refund endpoints) and **per-click and per-lead campaign creation (CPC/CPL)** carry an explicit trial allowance, so server-side tracking works during your trial. - **Bulk-email surfaces** (broadcasts, bulk affiliate invites, and migration auto-notify) require an active paid subscription and are not available during the trial. - **Payout runs** require an active paid subscription. Commissions accrue normally during the trial; the payouts page says "Payouts start once your subscription is active", and activating the plan opens payouts right away. See [Verification and payout reviews](/docs/brands/payouts#verification-and-payout-reviews). When the trial ends, Stripe charges the card on file and your subscription becomes active automatically; nothing changes in your dashboard. There is one edge case: a trial that was started but where Stripe Checkout was never completed (no card was ever entered). A daily job runs at 08:00 UTC, finds trials past their end date that never completed checkout, and moves those workspaces to a locked state; the dashboard then redirects you back to `/sign-up/plan` to pick a plan and enter a payment method. A separate job at 09:00 UTC sends the trial-ending reminder emails. ## Feature gating Some features are gated to specific plan tiers, enforced server-side. These gates check your plan tier the same way whether you are trialing or paying. As of today: - **AI co-pilot** (`/dashboard/ai-copilot`): requires Growth or higher. - **White-label embed** (`/api/embed/tokens`): requires Growth or higher. - **Custom domain** (first-party tracking plus the affiliate portal on a domain you own, e.g. `affiliates.brand.com`): included on every plan. Most affiliate platforms reserve first-party tracking for their most expensive plans; on Rekomi every tier gets it. One unified feature; see [Custom domain](/docs/developers/custom-tracking-domain) and [Settings](/docs/brands/settings). - **S2S tracking API** (`/api/tracking/s2s`): requires Starter or higher. - **Public REST API** (`/api/v1/programs`, `/affiliates`, `/conversions`, `/payouts`, `/dashboard` via `rk_live_*` keys): requires Starter or higher. The in-app dashboard at `app.rekomi.com` is unrestricted on every tier (only programmatic API calls are gated). - **Customer-facing webhooks** (`/api/webhook-endpoints`, the create / rotate / update / test routes): requires Starter or higher. Existing endpoints continue to fire after your trial ends; you just can't add new ones or reconfigure without a paid plan. - **Zapier integration**: requires Starter or higher. Uses the public REST API + webhook endpoints under the hood, so it inherits the same Starter+ gate. See [/docs/developers/zapier](/docs/developers/zapier). - **Audit log API export** (`/api/v1/audit-log` via API keys): requires Pro or higher. The dashboard audit-log view is unrestricted. - **Free branded affiliate portal** (`{yourbrand}.rekomi.com`): available on every tier, including Starter, with no setup. Putting that portal on a domain you own (`affiliates.yourbrand.com`) is also included on every tier as part of the unified Custom domain feature. See [Settings](/docs/brands/settings). Other features (Campaigns, Affiliates, Conversions, Payouts, Reports, Broadcasts) are available on every plan through the in-app dashboard; usage quotas (number of campaigns, number of affiliates, broadcasts per month, and a soft monthly partner-payout volume) vary by tier. The payout-volume figure is a guide, not a hard cap: see [Included payout volume](#included-payout-volume). ## Team-member limits Each plan tier caps how many users can join your organization workspace: | Tier | Team members | |---|---| | Starter | 2 | | Growth | 5 | | Pro | Unlimited | | Enterprise | Unlimited | The cap is enforced when you invite a new team member from `/dashboard/settings/team`. If you hit the cap, the invite returns HTTP 402 and prompts an upgrade. The cap is checked again when an invite is accepted; if the seats filled up in the meantime, the invite stays pending until a seat frees up or you upgrade. The seat-cap 402 uses a plan-limit error body and shows an inline upgrade prompt; the JSON body below is what tier-gated feature endpoints return. If you hit a gated endpoint on a lower plan, the backend returns HTTP 402 with a body like: ```json { "error": "plan_tier_required", "required": "Growth", "current": "Starter", "trialing": false } ``` The frontend catches this and shows the upgrade modal automatically. ## Included payout volume Each tier includes a monthly partner-payout volume, measured per calendar month across all campaigns: | Tier | Included partner payouts / mo | | --- | --- | | Starter | $2,500 | | Growth | $15,000 | | Pro | Unlimited | | Enterprise | Unlimited | Unlike the team-member cap, this is a soft guide, NOT a hard cap. Payouts are never blocked when you go over, and there is no overage fee: the flat 3% applies to all payout volume regardless of tier. Going over only triggers an upgrade nudge (an in-app banner and an optional email) suggesting more headroom; your payouts keep running unchanged. As the meter rolls out, the billing page will show your month-to-date payout volume against your plan's included amount. The authoritative figures live on [the pricing page](https://rekomi.com/pricing). ## Upgrading and downgrading `/dashboard/settings/subscription` shows your current plan and usage, with the plan picker right below, and links to the Stripe Customer Portal (one-click via `POST /api/billing/portal`) where you can change plan, update payment method, view invoices, or cancel. Stripe handles proration on its side. If you try to start a second Stripe Checkout while a subscription is already active, the API returns HTTP 409; for plan changes, use the Customer Portal. To cancel, use the "Cancel your free trial" or "Cancel your plan" link below the plan cards on the Subscription page: it deep-links straight to Stripe's cancel confirmation. A trial cancelled before its end date is never charged; a paid plan stays active until the end of the current billing period. ![The cancel link below the plan cards on the billing page](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/billing-cancel-link.png?v=1) ![The billing settings overview showing the current plan and usage](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/billing-overview.png?v=4) ## If a payment fails A failed charge never locks you out on the spot. Here is the exact lifecycle: 1. **Day 0.** The charge fails and your account goes past due. You keep full access. You get a payment-failed email stating the deadline, and a banner in the dashboard shows the same date with an "Update card" shortcut to the billing page's Payment method tab. 2. **Day 3.** If payment still hasn't gone through, a warning email repeats the cancellation date. 3. **Day 7.** The subscription is cancelled automatically. You get a final email explaining that the affiliate program is paused: your signup page shows "not accepting applications right now" instead of the form, your affiliates see a paused notice in their portal, and payouts stop running. Your links keep recording, and your campaigns, affiliates, and history stay intact; resubscribing from the Subscription page picks everything up where it left off. Some banks ask you to confirm a renewal instead of declining it (a 3D Secure check on a charge made while you are not on the site). Nothing is wrong with your card in that case. You get a "Confirm your payment" email with your bank's confirmation page, and the dashboard banner and plan chip show a "Confirm payment" button that opens the same page. Approving it in your banking app takes about a minute and your plan carries on; the same 7-day clock applies until it is confirmed. At checkout, Stripe only asks for a bank check when your card's issuer or its regulator requires one; we never force it on everyone. Fixing the card at any point before day 7 silently returns the account to normal; the countdown resets fully, so a future incident starts a fresh 7 days. Stripe retries the card on its own schedule during that week, so a card that starts working again is usually picked up without you doing anything. Stores billed through Shopify, Wix or BigCommerce follow their platform's own billing lifecycle instead: if the platform reports a billing problem (for example Shopify freezes the app subscription while a store's invoice is unpaid), Rekomi emails you and shows a banner pointing at your platform admin, where the fix lives. Rekomi never cancels a platform-billed subscription itself; the platform resumes or ends it based on how the billing issue resolves. ## Trial countdown In the final three days of your trial the dashboard shows a countdown banner above the main content; you can dismiss it for the rest of your session, except on the final day, when it stays until you pick a plan. The plan chip in the top nav shows your tier and trial status at all times. --- # Products and per-product rates Source: https://rekomi.com/docs/brands/product-commissions > Override the campaign rate for specific products, or assign a product to a partner who is paid on every sale of it, on rails that report line items. --- title: Products and per-product rates description: Override the campaign rate for specific products, or assign a product to a partner who is paid on every sale of it, on rails that report line items. --- Per-product commission rates let you set a different commission for individual products, overriding the campaign's default rate for just those items. Useful when some products carry thinner margins than others and you want affiliates paid accordingly. ## The product catalog Product rates live on the campaign's **Products tab**, together with which products affiliates can pick from: one surface answers "which products, shown to whom, paying what". Shopify brands see their store synced live, with a visibility switch per product controlling what appears in the affiliate picker, and a rate box for setting an override. On other billing providers you build the catalog by hand: a name, the page URL on your site, an optional photo, and an optional rate. The same editor is also available inside the Shopify embedded admin's Products panel, and Stripe brands can edit rates inside the Stripe App. - Rates are percentage-only, from 0 to 100. A 0% rate is how you stop a product from paying commission entirely. - A campaign can hold up to 200 product rates. - Saving replaces the whole set: whatever list you save becomes the complete list of product rates for that campaign. ## Which rails this applies to Product rates apply on the rails that report line items or the purchased product: Shopify orders, Stripe invoices, Paddle transactions, and Chargebee invoices (full line items), the purchased product on Gumroad sales, Lemon Squeezy orders, Polar orders, Creem one-time checkouts, Dodo Payments single-item carts, Mollie payments that carry order lines, and Braintree subscription plans. Not covered: Recurly, multi-item Dodo carts, Mollie payments sent without order lines, Lemon Squeezy renewals, and S2S postbacks or imported conversions, all of which use the campaign's normal rate; there is no line-item detail to match a product rate against. ## How the math works For each line item in a qualifying order or invoice: - If the line matches a rate you set, that line earns your rate. On Stripe, a rate on the specific price (`price_...`) wins over a rate on its parent product (`prod_...`), so you can pay a product's monthly and annual prices differently. - If nothing matches, that line earns the affiliate's normal resolved rate: whichever applies first among a per-affiliate override, a partner group rate, a tier rate, or the campaign default. Each line is rounded independently, away from zero. Product rates only apply when the resolved base rate is a percentage; they do not apply on top of a flat-fee rate. ## Assigned partners The **Assigned partners** card, below the product links on the Products tab, assigns a product to one partner. The same card is on the Products page inside the Shopify, Wix and BigCommerce apps and in the WordPress plugin, so you can manage it from wherever you run Rekomi. Every sale of that product is paid to that partner, with no affiliate link, coupon code or click involved. A campaign-wide default ("Everything else in this campaign is paid to") covers a storefront where one client owns every product. It suits a business that runs made-to-order storefronts for clients as the merchant of record and shares revenue with each one. Each client joins the campaign (or is invited) and is approved, their products are assigned to them, and Rekomi tracks their share and pays it on the normal payout schedule, with tax forms collected. Partners are picked from the campaign's approved affiliates, so a client is approved first. ### How it decides who is paid - A price id assignment beats a product id assignment, which beats the campaign-wide default. Assignments use the same ids as product rates (see [Product ids](#product-ids)). - An assigned product always credits its partner, even when the buyer came through another affiliate's link or used a coupon: this is a revenue share on the product, not marketing attribution. Unassigned products in the same order follow normal attribution (link, coupon, customer history), so one order can produce one sale per partner. - Shipping and tax are never part of a partner's basis. When line amounts exceed the paid total (a discount), each partner's basis is scaled down in proportion. - Each partner's sale is priced by the usual ladder: a per-product rate on that product if set, else the partner's override, group or tier rate, else the campaign default. Paying a client 100% of their products is a product rate of 100 on them, or a partner override of 100%. Assignments work on every rail listed above for product rates. On Shopify an assigned product is credited the moment the order is paid, never waiting on Shopify's journey data. A campaign holds up to 200 assignments, and saving replaces the whole set. ### Refunds A refund on a split order is shared across the partners' sales in proportion to each sale's amount, and each sale then claws back its own commission as usual. ## Product ids You can identify a product two ways: - **Shopify:** the numeric Shopify product id. You never type it: the dashboard has a built-in product search, and the Rekomi app inside your Shopify admin opens Shopify's own product picker. - **Stripe:** the Stripe Product id (`prod_...`) or a Price id (`price_...`). Use a price id when different prices of the same product should pay different rates; a price rate beats its product's rate, which beats the campaign default. - **Paddle:** the Paddle Product id (`pro_...`) or a Price id (`pri_...`); as on Stripe, a price rate beats its product's rate. - **Chargebee:** the plan, addon, or item price id that appears as the invoice line's entity id. - **Gumroad, Lemon Squeezy, Polar, Creem, Dodo Payments:** that platform's own product id. - **Mollie:** the order line's `sku`, or its `description` when the line has no sku, exactly as you send it in `lines[]` on the payment. - **Braintree:** the plan id, since Braintree tracks subscriptions by plan rather than by individual product. Manual entry of the id is available on every rail except Shopify, where you pick from the catalog instead. Product-name display is available everywhere except Dodo and Braintree, where only the id shows; give those a friendly label using the rate editor's name field. ## What affiliates see On Shopify-connected campaigns, the product search affiliates use to create [product & page links](/docs/affiliates/deep-links) shows a rate chip on each product: "Earns X%" highlighted on the products where you set a rate, and the campaign default rate muted on the rest (when your default commission is a percentage). Setting a higher rate on a product is how you steer affiliates toward it, and they see that incentive at the exact moment they pick what to promote. The links your affiliates create are listed in the campaign's Performance tab, Links section, with each link's own clicks and sales. The same line-item data that powers product rates also feeds the affiliate-facing per-link sales stats and the top-products report, so both sides can see which products are actually driving sales. ## See also - [Coupon-code attribution](/docs/brands/coupons): another way to attribute sales that stack with product rates on the underlying conversion. - [Limit a coupon to specific products](/docs/brands/coupons#limit-a-coupon-to-specific-products): scope a coupon code so it only applies to certain products. - [Per-affiliate overrides](/docs/brands/commission-models#per-affiliate-overrides): the partner-level rate that prices an assigned partner's sales when the product has no rate of its own. - [Payouts](/docs/brands/payouts): the schedule and tax forms that apply to assigned partners like any other affiliate. --- # Quickstart for brands Source: https://rekomi.com/docs/brands/quickstart > From signup to a working affiliate or referral program in about fifteen minutes. --- title: Quickstart for brands description: From signup to a working affiliate or referral program in about fifteen minutes. --- This guide walks you from a fresh signup through the 4-step setup checklist. Total time: roughly fifteen minutes. ## 1. Sign up and pick a plan Go to [rekomi.com/sign-up](https://rekomi.com/sign-up) and pick "I run a subscription business". You sign in with Clerk (email + password, Google, or other configured providers). After Clerk auth, you're routed straight to `/sign-up/plan`. Pick a tier (Starter $29, Growth $49, or Pro $99), pick monthly or annual billing, and optionally expand **Have a coupon code?** to apply a Stripe Promotion Code (validated via `POST /api/billing/validate-promotion-code`, rate-limited 5/min/user). Click **Start 14-day free trial** and you land in Stripe Checkout. Enter your card. Stripe creates the subscription with `TrialPeriodDays = 14`, so you won't be charged for the first 14 days. On success, you redirect back to Rekomi and your organization is provisioned with `SubscriptionStatus = Trialing` and the plan you picked. A card-on-file is required up front. The dashboard's onboarding gate redirects you back to `/sign-up/plan` if you try to reach `/dashboard` without an active subscription or trial. This is intentional: it ensures every brand on the platform has skin in the game from day one. One exception: invited team members are never asked to pick a plan or enter a card. They join through a dedicated invite sign-up and land directly in the inviting account, whose subscription already covers them (see [Team management](/docs/brands/team)). If you arrived from a specific pricing tier link (for example `/pricing?plan=growth&period=monthly`), `/sign-up/plan` pre-selects that tier and period. ## 2. Work through the setup checklist Plan selection happens in the signup funnel (Clerk signup → `/sign-up/plan` → Stripe Checkout) before you can reach the dashboard, so the in-dashboard checklist starts at step 1 below. After checkout you land on `/dashboard/setup`, the checklist home. It is guidance, not a gate: the sidebar and every other page stay available the whole time, you can do the steps in any order or leave and come back, and the only thing ever required up front is the subscription itself. A "Getting started" card pinned in the sidebar tracks your progress everywhere in the dashboard; each step in it is clickable and jumps straight to that step's page. 1. **Create campaign.** Name, slug, default commission, cookie window. Defaults are sensible if you're unsure. 2. **Branding.** Brand name, primary color, timezone, logo, and the platform your website runs on (Rekomi fills it in when it can detect it from your site). These render across affiliate-facing emails and the public application page, and the platform decides what step 4 looks like. 3. **Connect payment gateway.** Connect Stripe (or another supported gateway) so Rekomi can auto-track conversions from your billing webhooks. This is the sales-tracking side: it tells Rekomi which sales each affiliate drove. Payouts are then handled for you: Rekomi pays affiliates globally on Stripe Express (41 countries, including the US), direct bank deposits (60 more), and PayPal (31 more), 130+ countries combined. Rekomi takes a flat 3% on affiliate payouts and provides a year-end 1099-NEC candidate CSV. You can connect a gateway later from Integrations. 4. **Install tracking script.** Lands on `/dashboard/setup/install`, which leads with the one thing you need: your universal tracking snippet, with a dropdown that switches the paste form between HTML, Next.js, React, Vue/Nuxt, and Google Tag Manager. Below it, a "Not sure where to paste it?" searchable picker covers site builders, frameworks and code, and payment gateway guides, each opening a step-by-step recipe (42 platforms covered), plus a link to server-to-server tracking for any other backend and a one-line "Use your own domain" upsell. A **Share with a developer** button hands the whole step to whoever manages your site. Running a coupon-only program? A note at the top of the step says so: codes are attributed at checkout without the snippet, and **Skip this step** marks the step complete. If your website runs on beehiiv, Kit, MailerLite, Ontraport or GoHighLevel, this step changes shape: the platform connection under Integrations credits sales from the platform's own signup record, so the checklist asks you to connect the platform with **Credit sales** on instead of pasting a script (on MailerLite, plus one custom field Rekomi adds for you), and the script becomes an optional click-stats card that never blocks completion. ![The install step: the universal tracking snippet with the searchable platform picker below it](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/setup-install-hub.png?v=5) Steps check themselves off from real signals: a campaign that exists, branding that is saved, a payment gateway that is connected. The install step checks itself off once a real affiliate click or a recorded sale arrives, once your campaign has coupon codes, or when a verify-tracking scan finds the snippet; clicking **I'm done!** on the install page marks it complete as well. The sidebar's Getting started card can also be hidden for good from the X in its header, for everyone on the account; the Setup guide stays in the menu. On the five hosted platforms above, the connect-the-platform step checks itself off once the connection is made with Credit sales on. Below the checklist you will also see an optional "Track clicks from your own domain" card: it points at the custom-domain setup (first-party tracking, included on every plan) and never blocks completion; skip it and it stays skipped. If you told Rekomi at sign-up that you already run a program elsewhere, the checklist also ends with an **Import from** row named for that platform (for example **Import from Rewardful**, or **Import your affiliates** when you picked Other) and tagged Optional: it opens the importer for that platform and checks itself off once an import brings affiliates in. It counts as a step until it is done or dismissed, and the X on its row takes it out of the count, so setup can finish without it. You can re-open `/dashboard/setup` any time from the sidebar's Setup guide; once every step is done that nav row retires and the page becomes a small hub for the two ongoing pieces (payment gateway and tracking script). Plan + billing changes live at `/dashboard/settings/billing`. ## 3. Connect Stripe (if you skipped step 3) `/dashboard/integrations` shows your Stripe connection status. Click "Connect Stripe" and approve the Rekomi app for Stripe on the account you bill customers with. Rekomi requests read access to your subscriptions and invoices (for sales tracking). Payout runs are funded separately, from the payout method you save in Rekomi: you fund one payout run and Rekomi disburses the net to each affiliate. Affiliates are paid on Stripe Express (41 countries including the US), by direct bank deposit (60 more), or on PayPal (31 more), 130+ combined. The rail is picked automatically from each affiliate's country; there is nothing for you to configure per affiliate. ## 4. Open your campaign for applications From your campaign page, the slug controls the public application URL on your free branded subdomain: `https://{yourbrand}.rekomi.com/{your-slug}` (your root subdomain serves your primary campaign). Share this URL anywhere (email, Twitter, your homepage). Affiliates apply by filling in a short form, and are taken straight to create their Rekomi account (or to sign in) right after, so they never wait on an email to get started. If you set `autoApprove=true`, their referral link is live immediately. Otherwise their application lands in `/dashboard/affiliates` under the **Pending** tab for you to review, and their link unlocks the moment you approve. An approval email still goes out as a convenient one-click sign-in fallback. Every brand gets the `{yourbrand}.rekomi.com` branded subdomain free on all plan tiers. On every plan you can also publish the public landing page on your own domain (e.g. `affiliates.yourbrand.com`), which doubles as first-party tracking. See [Settings](/docs/brands/settings) for the DNS-verification flow. ## 5. What's next - [Campaigns](/docs/brands/campaigns): edit settings, configure recurring payouts, change attribution. - [Commission models](/docs/brands/commission-models): per sale, per click, per lead, recurring payouts, tiered commissions, per-affiliate overrides. - [Reports](/docs/brands/reports): once you have data, see clicks, sales, leaderboard, traffic sources. - [Free signups and leads](/docs/brands/tracking-and-attribution#free-signups-and-leads): if you have a free tier, credit affiliates for free signups that upgrade later. If you only have ten minutes after signup, finish steps 1-4 and share your application URL. Install tracking can wait until you have affiliates lined up. --- # Recruit affiliates Source: https://rekomi.com/docs/brands/recruit-affiliates > Three channels: public application page, direct invite (single or bulk email), and the curated network. --- title: Recruit affiliates description: "Three channels: public application page, direct invite (single or bulk email), and the curated network." --- There are three ways to bring affiliates into your campaign: public application page, direct invite, and the curated Rekomi Network. ## 1. Public application page (fully shipped) Every active campaign has a public application page on your free branded subdomain at `https://{yourbrand}.rekomi.com/{your-slug}` (your root subdomain serves your primary campaign). This is the primary channel for affiliate recruitment today, and it's free on every plan tier. The page shows: - Your campaign name and description - Commission type and value (transparent up front) - Cookie window - Attribution model - A short application form ![A public campaign application page on a branded subdomain](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/portal-public-page.png?v=3) The page is fully customizable in the campaign's signup page editor. Open it with the **Edit signup page** button next to your signup page URL at the top of the campaign. The application form itself is configured inside the editor. Out of the box it asks for email (required), full name, website or channel, and how the affiliate plans to promote you, with an **Apply** submit button whose text you can change. You can also add an optional custom alias field (3-64 letters, digits, or hyphens, with no leading or trailing hyphen) so affiliates pick their own link slug. You can add, remove, relabel, reorder, and mark fields required, choosing from 13 field types grouped as Text (text, long text, email, URL, number, date), Choice (dropdown, checkboxes, radio, a single consent checkbox), and Contact & location (phone with a flag/dial-code picker, country with the full ISO list, and a multi-part address). Submission creates a pending affiliate (or an approved one if you have `autoApprove = true` on the campaign); every answer is stored on the affiliate and shown on their detail page. Custom fields are also the right way to collect extra data for your own automations, such as a promo code, a social handle, or an internal reference: add a new field with its own key rather than relabeling a built-in field for a different purpose. Every answer, custom fields included, is delivered on the `affiliate.created` and `affiliate.approved` webhooks (and the Zapier triggers built on them) as an `applicationAnswers` object keyed by your field keys, so an automation in Make, Zapier, or your CRM can read the value directly; see [Webhooks](/docs/developers/webhooks#affiliatecreated). The full name answer in particular is reused across the product, in email greetings, payout receipts, and the `full_name` column of the US tax report export, so it should stay an actual name. Legal names for tax and payout purposes are always collected separately during payout setup (Stripe onboarding, or the affiliate's uploaded W-9/W-8), never from this form. The form can also run as a **two-step application**. Turn on the toggle in the form editor and drag your questions between the two sections: step 1 asks only for name and email, and submitting it creates the applicant immediately, exactly like a normal application. The confirmation card then asks the step 2 questions inline, and the answers attach to the same application. If someone stops after step 1 you still keep the lead, and their profile notes that step 2 is unfinished. Applicants can skip step 2, so treat those questions as useful context rather than a gate. The form editor also has a **message while an application is under review** field. On campaigns where you approve applications yourself, whatever you write there replaces the default text on the confirmation card an applicant sees right after submitting, so you can set expectations in your own words, for example how long a review usually takes. Applicants stay on that card until they choose to continue; nothing forwards them on a timer. Leave the field blank and the card shows the default copy, which asks the applicant to enter the code we email them so they can open their dashboard and follow the application. On pages where you have turned the "Powered by Rekomi" footer off, the card never mentions Rekomi in either case. ![The landing page editor where you customize the application form and page content](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/landing-page-editor.png?v=7) Beyond the form, the editor's Content tab also holds the **Campaign URL** group, where you can rename the page's URL slug (the old path redirects to the new one automatically). And the editor controls the whole page design: - **Layout**: pick from **Centered** (a classic single column), **Split** (your pitch beside a sticky application form), **Hero** (a full-width banner in your accent color behind the headline), or **Showcase** (a full-height brand image beside the page, with your program's key numbers in a benefits card). - **Theme**: start from a one-click preset, including dark palettes, then fine-tune the accent, body text, muted text, and page background colors. Choose from 1,900+ Google Fonts, set a background image with a readability overlay, and shape button and field corners. - **Content blocks**: stats (live commission, cookie window, and attribution values), how-it-works steps, rich text, FAQ, images, testimonials, a **Product showcase** (up to 6 products, each with an optional photo, price text, and link; only the name is required), and extra call-to-action buttons. The hero supports an eyebrow line above the headline and an "approved instantly" pill that only appears when the campaign actually auto-approves. Don't confuse this Product showcase block with the **Showcase** page layout above; the layout is the whole-page structure, the block is one section you can add to any layout. ![The editor's Design tab with the verified socials controls beside the live preview](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/landing-page-styles.png?v=5) Every change previews live beside the editor, and nothing goes public until you click **Save**. The page footer carries a small "Powered by Rekomi" link; brands on Pro and Enterprise can turn it off in the editor's Footer section. Share the URL anywhere: your homepage, your pricing page footer, Twitter, email lists, Product Hunt comments, your community. ### Verified social pills If you have connected your brand's social accounts in [Settings, under Verified socials](/docs/brands/settings#verified-socials), the signup page shows them under the headline as verified pills: platform icon, live follower count, and a check mark. It is free social proof for applicants deciding whether your program is real. The pills appear on every layout, and the About page shows them too. A website pill can join the row, and like every other pill it is verified, never self-reported. Brands using the Shopify, Wix, or BigCommerce app get it automatically: installing the app proves you control the store, and your platform already verified the store's domain, so the pill shows your store's own domain with nothing to set up (if you later claim a custom domain for your store, the pill upgrades by itself). Other brands unlock it by verifying a [custom domain](/docs/brands/settings#custom-affiliate-portal-subdomain): that one DNS record proves you own the domain and serves your signup pages first-party, beyond the reach of ad blockers, so the same setup does both jobs. Either way, if the website in your organization profile lives on the proven domain, the pill links that exact address instead. You control the pills per campaign from the page editor's Design tab, in the Brand section. "Show verified socials on this page" turns the whole row on or off, and under it each source (your website and every platform) gets its own toggle, so one campaign can show your main Instagram while another hides it. Social accounts you have not connected yet show a **Verify** button that takes you to Settings (inside the Shopify, Wix, and BigCommerce apps it opens the app's own Settings, then Verified socials). On the web dashboard, an unverified website row shows a **Verify domain** button into the custom domain flow; in the platform apps the website row is already verified with your store's domain. Once you finish verifying and come back, the row unlocks by itself (there is a Refresh button too). These toggles only affect that campaign's signup page. The About page always shows every connected account. ### Track your signup page with your own pixels If you drive paid or social traffic to your signup page, you can measure it in your own analytics and ad tools. The editor's **Tracking pixels** section takes IDs for five platforms, and each is just an ID, never a script: - **Google Analytics 4**: the measurement ID from GA4 under Admin, then Data streams (looks like `G-ABC1DE23FG`). - **Google Ads**: the conversion ID (looks like `AW-123456789`) and, optionally, the conversion label, both from the tag setup of a conversion action under Goals, then Conversions. The label is case-sensitive, so paste it exactly. - **Meta Pixel**: the numeric pixel ID from Meta Events Manager. - **TikTok Pixel**: the pixel code from TikTok Events Manager. - **LinkedIn Insight Tag**: the numeric partner ID from Campaign Manager under Measure, then Signals manager, then Insight Tag (it shows under "I will use a tag manager"), and, optionally, the ID of an event-specific conversion from Measure, then Conversion tracking. Paste an ID, save, and the public page starts loading that tag. Up to three events are reported: a page view, an "application started" event when a visitor begins filling in the form, and the conversion when they submit. - GA4, Meta, and TikTok get all three. The conversion uses each platform's standard registration event (`sign_up` on GA4, `CompleteRegistration` on Meta and TikTok), so you can optimize ad delivery against real applications, not clicks. - Google Ads gets the page view, which feeds your remarketing lists, and, once you add the conversion label, the submitted application as that conversion action. - LinkedIn gets the page view, which builds your retargeting audiences, and, once you add a conversion ID, the submitted application as that conversion. Two things to know: - **Visitors see a consent banner.** As soon as a tag is configured, the page shows a small cookie banner naming your brand. Tags only load after the visitor accepts; declining loads nothing at all, and a quiet "Cookie settings" link lets them change their mind later. Pages without tags never show a banner. - **Counts can sit below your Rekomi numbers.** Visitors who decline (or block trackers) still apply just fine, but their page views and applications never reach your ad platform. Rekomi's own application counts are always complete. IDs are validated when you save, so a typo fails loudly in the editor instead of silently never firing. A Google Ads conversion label or a LinkedIn conversion ID on its own is rejected too, because it can only fire alongside its platform's main ID. Each campaign's signup page carries its own tag settings, and the Integrations page (under Signup page tracking) lets you set one platform's IDs across every campaign in one place. If `autoApprove` is true, the applicant gets an instant approval email with a "Get your link" magic-link CTA that signs them straight into `/a` on one click. Otherwise the application lands in your approval queue (see [Manage affiliates](/docs/brands/manage-affiliates)) and the magic-link is generated after you approve. ## 2. Direct invite by email Click **Invite affiliates** on the affiliates dashboard at `/dashboard/affiliates`. The modal accepts a single email or a bulk paste of up to 50 addresses, a campaign to invite them to, an optional rich-text personal note (bold, italic, link, paragraph, list), and an optional commission override. Single invites can also carry a tracking slug so a migrating affiliate keeps their existing link. A third tab, **From customers**, suggests likely referrers from your existing Stripe customers (Growth plan and above). Submission creates an invite per recipient and dispatches a branded email to each: single invites send immediately, and bulk pastes are queued by a background job that sends at a steady rate so nothing trips email rate limits. ![The invite affiliates modal with email, campaign, personal note, and commission override fields](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/invite-modal.png?v=4) Each recipient gets a unique invite link at `/invites/affiliate/{token}`. Accepting the invite creates the affiliate as approved or pending, controlled by the modal's **Auto-approve on accept** toggle. The toggle defaults to whatever the selected campaign's auto-approve setting is. Once the affiliate is approved (whether by auto-accept or by your subsequent approve action), they receive the same "Get your link" magic-link email as a public applicant. One click into `/a`. Invites expire after 7 days; you can resend or revoke pending invites from the Invites tab on `/dashboard/affiliates`. ## 3. Curated network match You can apply your campaign to the **Rekomi Network**. The network is a vetted pool of creators who applied once to Rekomi and were approved as quality affiliates across multiple campaigns. Network creators see relevant campaigns in their dashboard and can apply with a pitch. Apply your campaign to the network at `/dashboard/network`. Listing needs an active paid plan and a funding source for payouts on your account, and the campaign's network switch cannot be turned on, and says why, until both are in place (see [Apply to the curated network](/docs/brands/network)). You fill in: - ICP description (who is your ideal customer) - Target affiliate profile (who is your ideal creator) - Commission pitch (why creators should care) - Support link (where creators get help) - Press link (your latest coverage, useful for creator vetting) - Notes (anything else) A Rekomi curator reviews your application and either approves it (your campaign goes into the network surface) or rejects it with a written reason. Status flows: `Submitted -> Approved or Rejected`; while submitted, the listing shows as Pending approval. The rejection reason is visible inline, and revising and saving the listing resubmits it for a fresh review. Once approved, creators see your campaign when they browse `/a/network`. They apply with a pitch; you approve or reject from your normal affiliates queue. ## Bonus channel: your affiliates recruit for you On every plan there is a fourth channel: [sub-affiliate recruiting](/docs/brands/sub-affiliate-recruiting). Enable it per campaign and every approved affiliate gets a personal recruitment link to share. When someone they recruit closes a sale, the recruiter earns an override you set: a percent of the recruit's commission, capped at one recruiting level, never multi-level. Recruits keep their full rate; the override comes out of your margin. It turns your strongest affiliates into a recruitment channel that scales with the program instead of with your outreach time. ## Which to use - **Just getting started?** Open the public application page. Share it everywhere. - **Have a list of known creators?** Send direct invites (single or a bulk paste of up to 50) with a personal note; invited creators skip the cold application step. - **Want scaled quality recruitment?** Apply to the curated network. Be ready to pitch creators on why your product is worth promoting. The three are not mutually exclusive. Most brands run all three in parallel. ## Settings that affect recruitment A few campaign settings change the recruitment experience: - `autoApprove = true`: applicants are approved on submit. Use this if you want zero friction and trust your audience. - `autoApprove = false`: applicants enter a queue. Use this if you want a quality bar. - `termsUrl`: link to your affiliate campaign terms. Shown on the public application page. - `description`: your pitch. Keep it punchy. Two or three sentences works. --- # Reports Source: https://rekomi.com/docs/brands/reports > The one analytics surface, KPIs, trends, breakdowns, and CSV exports, with four deep reports on its pill nav. --- title: Reports description: The one analytics surface, KPIs, trends, breakdowns, and CSV exports, with four deep reports on its pill nav. --- Reports at `/dashboard/reports` is your one analytics surface: performance KPIs, the trend chart, leaderboards, and breakdowns covering sale origin, geography, devices, funnels, and recurring revenue. The [Conversions page](/docs/brands/sales) is the raw event log; Reports is the rollup. A pill nav at the top hops between the overview and four deeper reports: - **Overview**: the whole program at a glance: clicks, sales, revenue. - **Campaigns performance**: every campaign side by side. - **Affiliates performance**: who drives clicks, sales, and revenue. - **Traffic and sources**: where clicks and sales come from. - **Sales and revenue**: funnels, timing, repeat buying, and recurring revenue. ![The Reports overview with KPI cards and the trend chart, and the pill nav for the four deep reports](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/insights-overview.png?v=8) ## Date range and campaign filter Every view shares the same header controls: - **Date range**: 7d, 30d, 90d, or 12m presets, plus a **Custom** range where you pick from and to dates. - **Campaign filter**: all campaigns, or one. Both live in the URL (`?range` and `?campaign`), so your selection survives switching between views, and a report link you share with a teammate opens with the same scope. The campaign filter applies to everything on the page, KPI cards and charts included; no section shows whole-account numbers while a campaign is selected. Two natural exceptions: Campaigns performance always lists every campaign side by side (that is its job; your selection is still kept for the other reports), and the columns in Affiliates performance are lifetime totals, which the page says itself; the date presets do not apply to that table. ## Overview Up to five KPI cards over the selected window: - **Revenue**: gross referred revenue, converted to your home currency. - **Sales**: how many referred sales landed, with the conversion rate. Denied and refunded sales are excluded, as are leads and click rewards. - **Leads** (shown only when you capture leads): signups captured in the window. - **Active affiliates**: affiliates with at least one click in the window. - **Clicks**: tracked clicks in the window. Below the cards: the tabbed trend chart (Revenue, Commissions, Sales, Clicks, plus Leads and Refunds; money tabs plot in your home currency), a **Top affiliates** leaderboard (each row links to the affiliate's detail page), a **Traffic sources** ranked bar list showing where clicks happened, grouped by channel (paid ads are named by network from their ad click IDs, and search, social, email, on-site navigation, and bot traffic are all told apart from plain referrer domains), and an uncredited-activity panel showing real sales that resolved to an affiliate who could not earn (it hides itself when there is nothing to show). The **Export daily sales CSV** button downloads the window's sales, honoring the campaign filter. ## Campaigns performance Every non-archived campaign side by side in a sortable table, so you can compare which campaigns actually drive clicks, sales, and revenue. ## Affiliates performance A stats table of your affiliates: who drives clicks, sales, and revenue. These columns are lifetime totals, not window-scoped. ## Traffic and sources Traffic sources (the same channel-grouped breakdown as the Overview card, with counts and percentages), plus four breakdowns: **Sale origin** (how sales were attributed: link, coupon, customer match, server, imported), **Geography** (clicks and sales by country), **Devices** (desktop, mobile, tablet), **UTM performance** (which utm_source and utm_medium values drive sales), and **Sub-IDs** (the channel tags affiliates add to their links, like `?sub=yt`, with clicks and sales per tag; sales count on every billing provider because the tag is stored on the sale itself, see [Sub-IDs](/docs/affiliates/tracking-link#sub-ids)). ## Sales and revenue The money-side breakdowns: earnings per click, average order value, and refund rate KPIs (plus lead-funnel KPIs when you capture leads), the click-to-sale **Funnel**, **Time to convert** (how long after the first click customers buy), **Attribution** (coupon against link), **Top products** (your best sellers by referred revenue, where your payment provider reports what sold), **Customers** (new against returning buyers), a day-of-week and hour-of-day activity heatmap, and **Recurring revenue** (renewal commissions and MRR for subscription campaigns). ## Programmatic access Everything on this page reads live queries; there is no overnight aggregation delay. The same numbers are available via the API: `/api/metrics/summary`, `/api/metrics/timeseries`, `/api/metrics/leaderboard`, `/api/metrics/traffic-sources`, and `/api/metrics/export.csv`, plus `/api/metrics/insights/*` for the breakdown sections, including `top-products`. For raw rows, use `GET /api/conversions` (see the [developer reference](/docs/developers/reference)). Affiliates get their own mirror of the overview at `/a/performance` scoped to their own stats, so the numbers you discuss with a partner always agree. --- # Conversions Source: https://rekomi.com/docs/brands/sales > The Conversions page, its Sales, Leads and Clicks tabs, statuses per type, the refund flow, and external event IDs. --- title: Conversions description: The Conversions page, its Sales, Leads and Clicks tabs, statuses per type, the refund flow, and external event IDs. --- A conversion is anything Rekomi records for an affiliate: a sale (a natively tracked payment-processor event or a server-to-server event that produces a commission), a lead, or a click reward. The Conversions page at `/dashboard/conversions` (older `/dashboard/sales` links redirect there) is the source-of-truth view for commission tracking: everything that has been credited to an affiliate. It opens on All, remembers the tab you used last in this browser, and the Sales, Leads and Clicks tabs narrow it to one type. ![The Conversions page with summary cards and a table of sales, leads, and recurring rows with source and status pills](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/sales-list.png?v=6) ## What you see The conversions table has these columns: - **Date**: when the conversion was recorded - **Affiliate** and **Campaign**: who earned it and the campaign it belongs to - **Type**: Sale, Recurring, Bonus, Lead, or Click - **Source**: how the conversion was attributed (see below) - **Amount**: the underlying order or invoice amount from the processor (or the amount you send via S2S) - **Commission**: the calculated commission in your currency - **Status**: per type. A sale reads In grace, Due, Paid, Refunded, Denied or Invalidated. A lead or click reads In grace, Due, Paid or Invalidated when the campaign pays for it, Not paid on this campaign when it does not, and Recorded for attribution when it is a free signup on a campaign that pays per lead (see Status meanings below). - **Stripe metadata**: subscription ID, customer ID, external event ID (where applicable) Tabs at the top switch between All, Sales, Leads and Clicks. The page opens on All and remembers the tab you used last in this browser. The Leads and Clicks tabs only appear once your account has recorded a lead or a click reward. ### Source (how a conversion was attributed) The **Source** column makes it obvious how each conversion was credited, so a coupon redemption is as visible as a link click. No setup is required; the tag is derived automatically from how Rekomi attributed the conversion: - **Coupon**: a redeemed affiliate coupon code. The code is shown inline (for example "Coupon · SARAH20"). - **Link**: a tracked click, or referral metadata carried on the payment processor. - **Customer match**: a returning customer or a prior lead matched to an affiliate. - **Click**: a pay-per-click reward rollup. - **Lead**: a lead or signup capture. A free identified signup carries no commission; on a per-lead (CPL) campaign, a qualified lead form submission earns the campaign's fixed per-lead fee. - **Override**: a sub-affiliate recruiter override row. - **Server**: a server-to-server (S2S) postback. - **Imported**: migrated from another platform. ## Status meanings Sales and paid leads or clicks share one commission vocabulary. A lead or click that carries no money shows a plain note instead of borrowing a money status. - **Pending** (shown as In grace): the normal state for a sale. The commission is calculated and becomes payable automatically once the refund-hold window passes, so the next payout run picks it up without anyone approving it. The pill keeps reading In grace; only an approved row reads Due. Default hold is 30 days from the date the sale was recorded; you can change the window in Payout settings (the grace window). Paid leads and clicks sit in the same window. - **Approved** (shown as Due): explicitly approved (via the API or an import). Like a matured Pending sale, it is payable. - **Paid**: appears on rows imported from another platform. For sales tracked in Rekomi, what has actually been paid out is shown on the affiliate's Payouts tab and in payout history rather than in this column. - **Refunded**: Stripe refunded the customer; commission was clawed back. - **Denied**: manually denied (via the API; the dashboard's manual controls are Validate and Invalidate). Rare. Typically only used for disputed sales where you have evidence the affiliate did not actually drive the customer. - **Invalidated**: quarantined as invalid traffic. A fraud-scored click or lead is quarantined automatically, and you can invalidate any row still in grace by hand, whatever its type. Not payable. It shows an **Invalidated** badge with a **Validate** action that releases a false positive. On a sale or a paid lead or click, Validate puts the row back in grace and the affiliate is told about the commission. On a row that never carried commission (Not paid on this campaign or Recorded for attribution), Validate simply returns it to that note; nothing becomes payable. See [Pay per click or lead](/docs/brands/cpc-and-cpl#keeping-click-and-lead-payouts-honest-cpc-and-cpl). - **Not paid on this campaign**: a lead or click recorded on a campaign that does not pay for that type. Rekomi keeps it for attribution (a later purchase by that lead is still credited to the affiliate), but it never carries a commission, so it never reads In grace or Due. - **Recorded for attribution**: a free signup captured on a campaign that does pay per lead. The signup itself carries no commission; a qualified lead on that campaign is a separate row with its own commission. Invalidate stays available on every row still in grace as a fraud tool, whatever its type. ## How sales are created Sale creation paths: 1. **Native processor webhook**. Most sales. Stripe, Paddle, Braintree, Shopify, WooCommerce, Wix, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie all connect natively: when a paid event fires (for Stripe, `invoice.paid`) for a customer referred by an affiliate within the cookie window, Rekomi creates a `Conversion` row automatically. 2. **S2S API**. For any other payment gateway, or events that do not flow through a connected processor. Your backend POSTs to `/api/tracking/s2s` with an `externalEventId` (for de-dup), `affiliateSlug`, `amountCents`, `currency`. See [S2S tracking](/docs/developers/s2s-tracking). 3. **Manual**. The **Add sale or bonus** button on the Conversions page (on an affiliate's detail page the same dialog opens from the **New commission** button on the Payouts tab, with that affiliate preselected) records a sale that happened outside your billing, or gives a one-off bonus commission. Pick the campaign and an approved affiliate (the seeded Test Affiliate is excluded so test rows never become payable), enter the sale amount, and Rekomi computes the commission from the campaign's rules; you can override the commission, capped at 5x the sale amount. Optional fields: customer email, a date (which backdates the record; a backdated sale already past your refund hold becomes payable right away), and a note. A bonus skips the sale amount and carries just the commission. Both follow your normal refund hold before becoming payable. Programmatically, the same action is `POST /api/conversions` (see the [API reference](/docs/developers/reference#conversions)). Not every signup is a paid sale on day one. Free signups are captured as **leads** (automatic for Stripe and Paddle, one line of code otherwise), so a later paid upgrade is still credited to the affiliate by email. See [Track leads and signups](/docs/developers/lead-tracking). ## External event IDs `externalEventId` is the de-dup key. For Stripe webhooks it is the Stripe invoice ID. For S2S it is whatever you supply. Within an organization, the same `externalEventId` will never create two sales; the second call is a no-op and returns `deduped = true`. This makes retried webhooks and replayed S2S calls safe. ## Refunds When Stripe sends a refund webhook (`charge.refunded` or `customer.subscription.deleted` within the refund-hold window), Rekomi claws the refunded share of the commission back: a partial refund reverses its proportional share, and the sale moves to `Refunded` once the refunds cover the full invoice. The affiliate's pending balance drops by the clawed-back amount. If the commission had already been paid out (rare with the default 30-day hold), the refunded share is owed back and the affiliate's next payout deducts it automatically; your workspace admins get a notification naming the paid commission and the amount, and you can also reverse that commission on Stripe from the payout's Detail page right away (see [Refunds after a payout](/docs/brands/payouts#refunds-after-a-payout)). The clawback happens automatically on every rail (Stripe, the stores, the other gateways): the platform's own refund event is the source of truth, so there is no manual refund button in the dashboard. ## Inspecting Stripe metadata Each sale row exposes the underlying Stripe identifiers (subscription ID, customer ID, invoice ID) so you can cross-reference in your Stripe dashboard. Useful for debugging "why was this affiliate credited" questions or for audit trails. ## Export The conversions table has a CSV export button in the top-right that downloads every row of the current tab (the Type, Source and Status filters narrow the table on screen, not the file); the file is named `rekomi-sales`, `rekomi-leads`, `rekomi-clicks` or `rekomi-conversions` to match the tab. The CSV includes `attribution_method`, `coupon_code` and `sub_id` as the last three columns, so coupon attribution and the channel tag survive the export. The table itself shows a Sub-ID column as soon as any loaded row carries a tag, and the API takes `subId=yt` (or `subId=untagged`) to filter by tag; see [Sub-IDs](/docs/affiliates/tracking-link#sub-ids). For programmatic exports, use the [Conversions read API](/docs/developers/reference#conversions): `GET /api/conversions?affiliateId=...&status=Approved&since=2026-01-01` returns paginated rows including `attributionMethod` and `couponCode`, and `kind=sale`, `kind=lead` or `kind=click` narrows it to one type the way a tab does. Pagination cursor `take` maxes at 200. (The page lives at `/dashboard/conversions`; older `/dashboard/sales` links redirect there.) ## Reporting view For aggregate metrics (clicks, sales, leads, conversion rate, top affiliates), see [Reports](/docs/brands/reports). The Conversions page is the raw event log; Reports is the rollup. The campaign cards on the Campaigns page count sales only in their sales 30d figure, with separate leads 30d and paid clicks 30d figures when the campaign pays for those. --- # Sending email Source: https://rekomi.com/docs/brands/sending-domain > Brand the emails Rekomi sends your affiliates and, optionally, send them from your own sub-domain with DKIM and SPF alignment. --- title: Sending email description: Brand the emails Rekomi sends your affiliates and, optionally, send them from your own sub-domain with DKIM and SPF alignment. --- Everything about the email your affiliates receive lives in **Settings > Sending email**: how the emails look (logo, From name, accent color) and, optionally, the domain they send from. ## Email appearance The Email appearance card controls the branding on every automated email and broadcast Rekomi sends your affiliates, with a live preview rendered by the exact template real sends use: - **From name**: the display name in the inbox From line. On white-label plans it applies immediately on our shared sender, no DNS needed; a verified sending domain (below) keeps using it. - **Email logo**: an email-only logo variant. Email bodies sit on a white card in every mail client, so a site logo drawn light-on-transparent disappears there; upload a dark or full-color version and emails use it while your site keeps its own logo. Without one, your site logo from Settings > Profile is used. - **Logo size**: Small, Default, or Large. - **Accent color**: buttons and accents follow your brand color from Settings > Profile. Use **Send me a test** to get a real sample in your own inbox. In the Shopify, Wix, BigCommerce, and WordPress apps the same card lives under Settings > Email appearance. ## Sending domain By default, affiliate-facing email is sent from `notifications@mail.rekomi.com`. That works, but it has two costs: affiliates may flag it as unfamiliar and inbox placement is tied to Rekomi's shared sending reputation. A custom sending domain solves both. Verify a sub-domain you own and Rekomi will send those emails from your domain with DKIM and SPF alignment (with DMARC recommended on top; see below). The brand keeps using Rekomi's underlying email infrastructure (Resend); only the visible sender changes. ## Before you start You need: - A sub-domain you control (e.g. `partners.yourbrand.com`). We recommend a sub-domain rather than the apex (`yourbrand.com`) so this setup never collides with email your team already sends. - DNS provider access (Cloudflare, Route 53, Namecheap, GoDaddy, etc.). - About 15 minutes for DNS propagation. You do not need: - A Resend account. Rekomi pays for the underlying sending; this is included with your Pro plan at no extra cost. - A dedicated sending IP. Sending uses Resend's shared pool today. ## Connect your sender domain 1. Open **Settings → Sending email** (`/dashboard/settings/sending-domain`) in the Rekomi dashboard. 2. Enter the sub-domain you want to send from (e.g. `partners.yourbrand.com`). 3. Enter the sender name your affiliates will see (e.g. `Yourbrand Affiliate Campaign`). 4. Optionally set a reply-to address. It applies from your next email, even while the domain is still verifying. When omitted, replies go to your account's billing email once the domain is verified, and to Rekomi support until then. 5. Click **Register sender domain**. Rekomi creates a domain entry in Resend and returns a small set of DNS records you need to add. ![The sending domain setup form with sub-domain, sender name, and reply-to fields](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/email-sender-setup.png?v=4) ## Add the DNS records The records Rekomi provides cover: - **DKIM** (CNAME or TXT). Cryptographically signs your outbound mail. - **SPF** (TXT). Tells receivers that Rekomi's sending infrastructure is authorized. **DMARC** is recommended but not included in the returned records: it defines policy when DKIM or SPF fails, and you add it yourself as a `_dmarc` TXT record on your sub-domain (a relaxed starting policy like `v=DMARC1; p=none;` is fine) once DKIM and SPF verify. Copy each record's name and value into your DNS provider. Set TTL to "Auto" or 300 seconds. > Most DNS providers strip the trailing dot on the record name. Both forms work. ## Verification After you add the records, click **Check now** in the Rekomi dashboard. Rekomi asks Resend to re-check DNS and shows the result. DNS propagation usually takes 5 to 15 minutes. Rekomi automatically polls every 30 seconds while the verification is pending, so you can leave the page open. Once Resend confirms every record, the status flips to **Verified** and the dashboard shows a preview of how the From header will look to affiliates. ## What gets sent from your domain After verification, the affiliate-facing emails go from your domain: - Affiliate invitations - Payout sent notifications - Application approved (welcome) emails ## What still sends from Rekomi A small set of emails continues to send from Rekomi's own sender: - Payout failed notifications (these go to your billing contact, not the affiliate, so you can act on them) - Sign-in flows (handled by our auth provider, not Rekomi) - Trial expiry warnings (sent to you, not affiliates) - Billing notifications from Stripe (handled by Stripe directly) - Legal / compliance notifications (when we need to reach you as an account holder, not as a brand) This split is intentional: your sender domain is for emails to **affiliates**. Account-level emails to **you** stay on the Rekomi infrastructure so you can always reach support and we can always reach you. ## Troubleshooting **Status stays Pending for more than 30 minutes** DNS propagation can take longer for some providers. Check the records directly with `dig` or `nslookup`, using the exact record name shown in the dashboard: ``` dig TXT ``` If the records do not return, double-check they were saved in your DNS provider. Some providers require you to wait a few minutes between save and propagation. **Status flips to Failed** Resend's DNS check found a problem. Look at the **Status** column in the records table. Records showing **Failed** are the ones that need attention. Common causes: - Record value was pasted with extra whitespace or quotes - DNS provider auto-appended your domain to the record name, producing a doubled name like `record.partners.yourbrand.com.partners.yourbrand.com` - TTL set too high; lower to 300 seconds After fixing, click **Check now**. **One record verified, another did not** Each record verifies independently. The domain as a whole only verifies when every record is confirmed. Check each row's status. ## Disconnecting To stop sending from your domain: 1. Open **Settings → Sending email**. 2. Click **Disconnect**. 3. Type `DISCONNECT EMAIL SENDER` to confirm. The Resend domain entry is torn down and affiliate emails revert to sending from `notifications@mail.rekomi.com` on the next send. You can reconnect later. ## FAQ **Does this cost extra?** No additional fee. The sending domain feature is included in the **Pro tier** alongside full white-label removal (remove Rekomi branding). For reference: the custom tracking domain (first-party tracking plus the affiliate-portal subdomain) and customer-facing webhooks are available on every plan. Starter and Growth tiers see a Pro upgrade prompt on the settings page for the sending-domain feature specifically; accounts still in the 14-day trial can use it during the trial. **Can I use the apex of my domain (`yourbrand.com`)?** We strongly recommend a sub-domain (e.g. `partners.yourbrand.com` or `affiliates.yourbrand.com`) so DKIM, SPF, and DMARC for your existing email never collide with the Rekomi-sent affiliate mail. The apex is accepted, but risky for exactly that reason. **What is the From address exactly?** ` >`. The local part is locked to `noreply` in v1; we may allow customization in a future release. **Can affiliates reply?** Yes. Replies go to whatever **Reply-to** you set during setup, from the first email after you save it, even while the domain is still verifying. If you left it blank, replies go to the billing email on your Rekomi account once the domain is verified, and to Rekomi support until then. **Will my domain reputation be affected?** Your domain inherits the deliverability characteristics of Resend's shared sending IPs. For 95% of brands this is a net improvement over the default. Dedicated IP isolation is on the roadmap. **Can I send marketing campaigns from this domain through Rekomi?** To your affiliates, yes. Rekomi sends both the automated affiliate lifecycle emails (invitations, approvals, payout notices, and the rest of the catalog on each campaign's [Emails tab](/docs/brands/emails)) and your [broadcasts](/docs/brands/broadcasts) to affiliates, and all of them go out from your custom sending domain once it is verified. What Rekomi does not do is email your customers; for marketing to your customer list, use a dedicated tool (Customer.io, Loops, etc.). --- # Organization settings Source: https://rekomi.com/docs/brands/settings > Profile, brand color, timezone, API keys, audit log, webhooks. --- title: Organization settings description: Profile, brand color, timezone, API keys, audit log, webhooks. --- The settings area at `/dashboard/settings` opens from the sidebar's **Account settings** row or your avatar menu's **Account settings** item, and uses a grouped left rail: - **Account**: Profile, Sign-in & security, Team, Subscription, Billing, Payout schedule, Sending email, Notifications - **Tracking**: Website snippet, Payment gateway, Custom domain, Link tags, Move from another platform - **Connections**: Integrations - **Developers**: API Keys, Connect an AI assistant, Connected apps, Webhooks - **Security**: Audit Log ![The settings area with the grouped sidebar, open on the Profile page](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-profile.png?v=5) This page covers the most-used sections. Payouts has its own guide ([Payouts](/docs/brands/payouts)), notifications are covered in [Notifications](/docs/brands/notifications), sending from your own email domain in [Sending email](/docs/brands/sending-domain), and attribution params in [Tracking and attribution](/docs/brands/tracking-and-attribution). ## Profile `/dashboard/settings/profile` Edit your organization profile. Fields: - **Organization name**: shown to affiliates on the public application page and in transactional emails. - **Description**: shown on your landing page and the creator network. - **Industry**: your business category. - **Billing email**: where Stripe invoices go. - **Timezone**: IANA timezone (UTC by default). Affects timestamps shown across the dashboard and email sends. - **Primary brand color**: hex like `#0E7C7B`. Used in transactional email templates and (in future) the public application page accent. - **Logo**: drag and drop or choose a file to upload. Used in transactional email templates. - **Favicon**: optional; falls back to the logo. - **About text, founded year, website**: power your portal's About page at `{your-subdomain}.rekomi.com/about`, alongside your discoverable campaigns and their real terms, and a payout trust line. The About text is rich text (bold, italics, lists and https links; up to 2000 characters), edited here or in the campaign page editor's About tab, which also holds an optional custom section. Save. Changes apply immediately. ### Verified socials Below the profile form, the **Verified socials** card lets you connect your brand's social accounts with OAuth. Connecting proves ownership, and each connected account shows on your public signup page and About page as a verified pill: the platform icon, your follower count pulled straight from the platform, and a check mark. Visitors see platform-verified numbers, not self-reported ones. Click **Connect** on a platform and approve the request on the platform's own screen; you land back here with the account connected. Facebook asks you to pick a Page when you manage more than one. Follower counts refresh automatically. **Disconnect** keeps the last verified numbers greyed out; **Remove** deletes the connection and its history outright. Two notes: - The signup page pills are controlled per campaign in the page editor (Design, then Brand): one switch for the whole row, plus a per-source toggle for your website and each connected account, so different campaigns can show different pills. Unconnected social accounts show a Verify button there that brings you back to this card. The About page always shows connected accounts. - The website pill is verified too, never self-reported. If you run Rekomi through the Shopify, Wix, or BigCommerce app, it is automatic: your store's own domain (the one your platform verified for you) appears as the pill with nothing to set up. Otherwise it appears once you verify a [custom domain](#custom-affiliate-portal-subdomain), whose DNS record proves you own the domain. Either way, if the website in your profile lives on that same domain, the pill links your exact website instead. - Connecting works inside the Shopify, Wix, and BigCommerce apps too: open Settings, then **Verified socials**, and click Connect. The platform's sign-in opens in a small popup window and returns you to the admin automatically once you approve. If your browser blocks the popup, allow popups for your store admin or use the web dashboard at app.rekomi.com instead. Either way, the pills appear on your pages no matter where you manage the rest of Rekomi. ## Team `/dashboard/settings/team` Invite, manage, and remove team members. Four roles (Owner, Admin, Manager, Viewer). See [Team management](/docs/brands/team). ## Subscription `/dashboard/settings/subscription` Your plan lives here: current tier, status, and usage at the top, with the plan picker right below for switching tiers (a quiet cancel link sits under the cards). The **Manage in Stripe** button opens the Stripe Customer Portal, where you can change plan, update payment method, view invoices, or cancel; Stripe handles proration on its side. See [Plans and trials](/docs/brands/plans-and-trials) for the full lifecycle. New brands hit `/sign-up/plan` first (after Clerk auth) to pick a tier and complete Stripe Checkout before they can reach `/dashboard`. The onboarding gate redirects a brand without an active subscription or trial back to `/sign-up/plan`, except invited team members, who join through the invite flow and are never asked to pick a plan or enter a card (the account they join already carries the subscription). ## Billing `/dashboard/settings/billing` The money records, one tab each. **Invoices** lists your last 12 invoices with PDF links. **Fees ledger** is the running ledger of payout charges, affiliate payouts, and reversals. **Payment method** shows the card on file with an **Update card** button that one-clicks you into the Stripe Customer Portal. **Payout funding source** is how payout runs are funded: a bank debit in your region, or bank-transfer funding from anywhere. It is also one of the two things a creator network listing needs, alongside an active paid plan (see [Apply to the curated network](/docs/brands/network)). **Currency** holds the home-currency selector (see [Multi-currency](/docs/brands/multi-currency)). ## API Keys `/dashboard/settings/api-keys` Create, rotate, and revoke API keys. Each key has: - **Name**: human-readable label. - **Prefix**: the first characters of the key (`rk_live_...`), shown in the list so you can tell keys apart. - **Plaintext bearer**: shown EXACTLY ONCE at creation. Copy it now or you regenerate. Stored as SHA-256 hash on our side. - **Plaintext signing secret**: also shown EXACTLY ONCE. Used for S2S tracking signatures. Encrypted at rest. ![The API keys settings page listing keys with their prefixes, last-used dates, and status](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-api-keys.png?v=4) See [Developer authentication](/docs/developers/authentication) for usage. ## Webhooks `/dashboard/settings/webhooks` Subscribe to outbound webhook events. Per endpoint: - URL (HTTPS) - Event types (`*`, `conversion.created`, `conversion.approved`, `payout.created`, `payout.paid`, `affiliate.created`, `affiliate.approved`) - Signing secret (HMAC-SHA256, shown once at creation/rotation) Subscribing to `*` also delivers additional event types (refunds, denials, payout failures, coupon events); see [Developer webhooks](/docs/developers/webhooks) for the full catalog. A background delivery service sends events to your URL. Failed deliveries retry with exponential backoff up to 5 attempts. Per-endpoint success rate is visible in the list (last 100 deliveries). Click any endpoint to expand its delivery history. ![The webhook endpoints settings page with an endpoint and its subscribed events](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-webhooks.png?v=4) See [Developer webhooks](/docs/developers/webhooks) for verification code. ## Audit Log `/dashboard/settings/audit-log` Read-only log of every admin action in your organization. Filter by: - Action type (`program.created`, `affiliate.approved`, `payout.batch.created`, `webhook.endpoint.created`, etc.) - Entity type (program, affiliate, payout, webhook_endpoint, organization) - Date range - Actor (which team member did it) Expand any row to see the JSON diff (before / after). ![The audit log with filterable admin actions](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-audit-log.png?v=5) Click "Export CSV" with filters applied to get a CSV. Exported cells are guarded against spreadsheet formula injection. Retention is currently indefinite. Per-tier retention windows are on the deferred list. ## Migration `/dashboard/settings/migrate` ![The migration source picker with the 22 supported sources](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-migrate.png?v=4) If you are coming from another affiliate platform, this tab is where you bring your existing affiliate roster across. Importers ship for 22 sources: API pulls for Rewardful, Affonso, FirstPromoter, Tolt, Lemon Squeezy, impact.com, PromoteKit, LeadDyno, Trackdesk, Partnero, UpPromote, GoAffPro, Refersion, and Everflow (most also bring leads, lifetime click counts, and commission history in the same run), and CSV uploads for Tapfiliate, PartnerStack, Dub Partners, Gumroad (whose sales history comes from a follow-up API pull), ReferralCandy, BixGrow, Shopify Collabs, and Post Affiliate Pro (both its canonical semicolon export and its grid exports parse directly), all provisioning affiliates plus their default tracking links in one pass. Optional auto-notify (Growth tier or higher) batch-emails every imported affiliate a brand-customized invite so they can claim their new Rekomi account. Plan tier requirements: - **Starter or higher** to run the importer (trial-eligible) - **Growth or higher with an Active subscription** to fan out auto-notify emails (trial users can compose and preview but cannot send) See the full step-by-step guide on [Migrations from other platforms](/docs/brands/migrations). ## Custom affiliate-portal subdomain Every brand gets a free branded affiliate portal at `{your-brand}.rekomi.com` on every plan, including Starter. Affiliates sign up there: your primary campaign at the root, each campaign at `{your-brand}.rekomi.com/{campaign}`. On every plan, you can serve those same pages from a domain you control (e.g. `affiliates.yourbrand.com`) instead. Affiliates see your own domain in the URL bar; Rekomi handles the routing transparently. Configure the custom domain in **Settings → Custom domain** (the campaign detail page links there). The page has two tabs: **Custom domain** (the default, with the guided setup below) and **Hosted landing page**, where you pick the free `{your-brand}.rekomi.com` subdomain those pages start on. One domain covers your whole program across every campaign. The flow: 1. Enter the subdomain you want (`affiliates.yourbrand.com`). 2. Rekomi detects where your DNS is managed. If that provider supports one-click setup (Cloudflare today), the card leads with **Add it for me**: approve the record on their consent page (it is created DNS only, at the right subdomain) and you land back on the card, which verifies everything automatically. Otherwise, copy the CNAME record (`cname.rekomi.com`) shown on the card and add it at your DNS provider by hand. 3. Click **Verify** (already done for you if you used **Add it for me**). Rekomi runs a DNS chain resolution to confirm the CNAME points at the right host; if DNS has propagated (usually a few minutes) verification completes right away. The domain is then auto-attached to our edge and an SSL certificate provisions automatically (usually within a few minutes). ![The custom domain settings page offering the one-click Add it for me option](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-custom-domain.png?v=5) Once verified, your primary campaign's public application URL is `https://{your-domain}/` and each campaign lives at `https://{your-domain}/{campaign}`. These are clean paths, one per campaign, serving the same landing page renderer that powers your free `{your-brand}.rekomi.com` portal. Verifying the domain does one more job: it proves you own the domain (adding the CNAME requires control of your DNS), so your website appears as a **verified pill** on your signup and About pages next to your verified socials. No separate setup; the same DNS record covers both. Plan gate: none. The unified "Custom domain" feature (portal plus first-party tracking on one host) is included on every plan; most affiliate platforms reserve first-party tracking for their most expensive plans. The free `{your-brand}.rekomi.com` portal also stays available on every tier. --- # Stripe Connect (sales tracking) Source: https://rekomi.com/docs/brands/stripe-connect > Connect your Stripe account once and Rekomi tracks every sale and refund automatically. How the OAuth connect works, how attribution pairs with click tracking, and a technical overview of the webhook rail. --- title: Stripe Connect (sales tracking) description: Connect your Stripe account once and Rekomi tracks every sale and refund automatically. How the OAuth connect works, how attribution pairs with click tracking, and a technical overview of the webhook rail. --- Stripe Connect is the default and simplest way to feed sales into Rekomi. You authorize Rekomi to read your Stripe account once, and from then on every paid checkout and every refund flows into Rekomi automatically. No thank-you-page snippet for the sale event, no per-sale reconciliation, no spreadsheet at month end. This is what most brands use, because if your billing already runs through Stripe, the conversion backend is one click. > **Manage it from inside Stripe.** The [Rekomi app for Stripe](/docs/brands/install/stripe-app), approved for the Stripe App Marketplace and rolling out now, puts attribution, commission approvals, and payout status directly in your Stripe Dashboard. Same native tracking, different entry point. ## The two halves of tracking This is the one thing worth understanding up front, because it explains why connecting Stripe is not always the *only* step: 1. **The sale signal (Stripe Connect).** Connecting your Stripe account tells Rekomi *that a sale happened*, its amount, currency, and whether it was later refunded. The amount Rekomi records is the pre-tax amount: when Stripe reports a tax line on the payment, that tax is left out, so commissions are never paid on tax you collect. 2. **Attribution (click tracking or coupons).** Connecting Stripe does not, on its own, tell Rekomi *which affiliate* to credit. That identity rides along on the sale, and it gets there in one of two ways: - the click cookie set by the [tracking snippet](/docs/brands/install), forwarded onto the subscription or invoice as `rekomi_affiliate_id` metadata (see [Install on Stripe](/docs/brands/install/stripe)), or - a [coupon code](/docs/brands/coupons) the affiliate shares, which Stripe records on the sale, or - on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel, the platform connection under Integrations, which reads the tag the platform stored on the customer at signup. So Stripe Connect is the conversion **backend**, and the snippet or coupons are the **attribution** layer. That is why onboarding marks "Connect payment gateway" as done once Stripe is linked, but still walks you through installing the snippet: the snippet is what names the affiliate. A sale that arrives with no attribution metadata, no affiliate coupon, and no prior attribution history for that customer is recorded by Stripe but simply has no one to credit. ## How to connect In setup, the **Connect payment gateway** step (`/dashboard/setup/connect-sales`) has a "Connect Stripe" button: one click, no interstitial page in between. Connecting Stripe there completes the conversion-tracking backend; affiliate payouts are set up separately, from your payout settings. You can also reach it later from your settings. ![The connect payment gateway step listing all eleven supported gateways: Stripe, Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/connect-sales-processors.png?v=5) Click it and you are sent to Stripe's hosted consent for the [Rekomi app](/docs/brands/install/stripe-app), where you pick the Stripe account you already use to bill customers and approve the app's permissions. That single authorization is the connection: Stripe sends you back to the dashboard with the account linked, your sale and refund events start flowing, and the app's panels appear inside your Stripe Dashboard as a bonus. `/dashboard/setup/payments` is where the connection lands and where you manage it afterwards. Status after connecting: - **Not connected**: no Stripe account linked yet. - **Connected**: Rekomi is receiving your sale and refund events. The dashboard shows the connected account id (`acct_...`). One Stripe account links to exactly one Rekomi organization. If you reconnect a different Stripe account later, Rekomi drops the old connection and stops receiving its events; uninstall the Rekomi app from the old account's Stripe Dashboard to remove the grant there too. ## What Rekomi does with your events Once connected, Stripe delivers your account's events to Rekomi through the Rekomi app's event stream. Rekomi reacts to: - **Sales** (successful invoice payments): if the sale carries an affiliate (via metadata, a coupon, or the customer's attribution history), Rekomi records a conversion when the invoice is paid and the commission lands on that affiliate's ledger. - **Refunds** (`charge.refunded`): Rekomi reverses the commission proportionally and updates the affiliate's pending balance the same day. There is no "mark as refunded" button because Stripe is the source of truth. Subscription cancellations (`customer.subscription.deleted`) are recorded for reporting but do not claw anything back: commission already earned stays earned unless the underlying charge is refunded. See [Tracking and attribution](/docs/brands/tracking-and-attribution#refund-aware-attribution). Duplicate event deliveries are no-ops: each Stripe event id is processed once. ## What Rekomi can and cannot see Connecting grants Rekomi read access to your account's billing activity (charges, invoices, subscriptions, refunds). Rekomi does not see your customers' card numbers, and the connection never moves money in or out of your Stripe account: affiliate payouts are funded from the payout method you save in Rekomi, not from your Stripe balance. You can revoke access at any time (see Disconnect below). ## Disconnect and reconnect To disconnect, uninstall the Rekomi app from your Stripe Dashboard (Settings, then Installed apps). Your Stripe account itself is untouched; Rekomi simply stops receiving new events. Sales already recorded stay on the affiliate ledgers, and re-installing the app restores the connection to the same workspace. To switch to a different Stripe account, run the connect flow again and pick the new account in the consent screen. ## When Stripe Connect is not the right backend Stripe Connect is the best fit when your sales run through Stripe. If they do not, or you cannot connect, use one of the alternatives. They are mutually compatible; pick what matches where your sales originate: - **[Server-to-server tracking](/docs/developers/s2s-tracking)**: your backend posts each conversion to Rekomi directly. Best for mobile in-app purchases, custom checkout, or non-Stripe billing. - **[Coupon codes](/docs/brands/coupons)**: each affiliate gets a unique code; redemptions attribute automatically. Works with or without Stripe Connect, and needs no code on your site. - **[Browser pixel](/docs/developers/pixel-tracking)**: for non-Stripe checkouts where you cannot run server-side code (Webflow, Memberstack, Lemon Squeezy). - **[Platform connection](/docs/developers/lead-tracking#leads-and-sales-from-your-platform)**: on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel, the CRM connection credits sales from the platform's own signup record, and for Kit Commerce and Ontraport it brings the sales in as well. ## Technical overview For developers and the technically curious, here is what happens under the hood. ### Connect (authorization) Workspaces authorize through the Stripe Apps OAuth flow: `POST /api/integrations/stripe-app/authorize` builds the marketplace consent URL for the Rekomi app and redirects the brand to it. Approving installs the app on the chosen account, and the callback binds that account (`acct_...`) to your organization with account-scoped OAuth tokens. The `state` parameter is an encrypted, signed token (ASP.NET Data Protection) bound to your organization and a single-use, 10-minute nonce, so the callback cannot be replayed or swapped across organizations. A unique index guarantees one Stripe account maps to one organization; re-running the consent for the same account heals the existing binding (fresh tokens) instead of creating a duplicate. ### Event ingestion (the webhook rail) Rekomi exposes a single Stripe webhook endpoint that verifies each delivery against **two** platform-level signing secrets: one for Rekomi's own platform billing and one for the **application webhook** that carries events from every account with the Rekomi app installed. Because verification is against platform secrets, **you do not configure a Stripe webhook of your own** for Rekomi to receive sales. Installing the app is what enrolls your account into the event stream. This is only about Rekomi *receiving* events from Stripe. If you want Rekomi to push conversions back *out* to your own systems (to mirror attribution, sync to a data warehouse, etc.), that is a separate, optional Rekomi feature: see [outbound webhooks](/docs/developers/webhooks) and [Zapier](/docs/developers/zapier). Every coupon, link, and S2S conversion is also visible in your dashboard with a "Source" tag regardless, so most brands need no integration at all. For a connected-account event, Stripe sets the event's `account` field to your `acct_...`. Rekomi resolves your organization by matching that against the stored connected account id, then routes the event to conversion or refund handling. Signatures are checked with a 300-second replay tolerance, and processed event ids are deduplicated, so retries and replays are safe. ### Attribution precedence When a sale event arrives, Rekomi resolves the affiliate in this order: 1. An affiliate coupon on the sale, when the campaign is set to let coupons win over click attribution. 2. `rekomi_affiliate_id` in the subscription or invoice metadata, stamped at checkout from the click cookie. 3. The customer's attribution history: renewals and repeat purchases credit the affiliate who originally referred that customer. 4. An affiliate coupon on the sale when the campaign is not set that way (used only if nothing above resolved). 5. An email match against a lead the affiliate previously recorded. 6. The platform lookup: on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel connections with **Credit sales** on, Rekomi reads the tag the platform stored on the customer at signup. Always last, never an override. If none resolve, the sale has no attributable affiliate and no commission is created. This is why the [click snippet](/docs/brands/install/stripe) (which stamps the attribution metadata) or [coupons](/docs/brands/coupons) are required alongside Stripe Connect, not optional polish, except on those five platforms, where the connection carries the attribution and the snippet only adds click counts. ## Related - [Install on Stripe](/docs/brands/install/stripe): stamping attribution metadata onto your Checkout Sessions, and coupon-code attribution for Payment Links (the attribution half) - [Tracking and attribution](/docs/brands/tracking-and-attribution): the full model across all sources - [Payouts](/docs/brands/payouts): how affiliate payouts are funded and dispatched - [Coupon-code attribution](/docs/brands/coupons) --- # Recruiting (sub-affiliates) Source: https://rekomi.com/docs/brands/sub-affiliate-recruiting > A 2-tier program, affiliates recruit affiliates and earn a bounded override, one recruiting level only. Not MLM. --- title: Recruiting (sub-affiliates) description: A 2-tier program, affiliates recruit affiliates and earn a bounded override, one recruiting level only. Not MLM. --- Recruiting (sub-affiliate recruiting) lets an existing affiliate (A) recruit a new affiliate (B) through a personal recruitment link. When B converts a sale, A earns a small override on top of B's normal commission. B still earns the same headline rate they always did; the override comes out of your program budget, not B's pocket. Use it when: - You want your strongest creators to seed the program for you. - Your audience is creator-adjacent (podcasters, YouTubers, course operators). - You're willing to pay a finder's fee for high-quality referrals at the cost of a small extra commission line. ## Not MLM. One recruiting level. Full stop. This is the part to read carefully before turning it on. - **One recruiting level, hard-capped.** A recruits B. A earns an override on B's sales. That's it. If B then recruits C, A earns nothing on C's sales. B earns nothing on C's recruits-of-recruits, because there are none to earn from beyond a single hop. - **No chain propagation.** Overrides never cascade up a tree. The data model cannot represent more than one upstream recruiter per affiliate: each affiliate record stores at most a single recruiter reference. - **Single override layer.** Each sale produces at most one override row, paired with at most one parent commission row. - **Primary earnings come from the affiliate's own sales.** B earns commission on the sales B drives. A earns commission on the sales A drives. The override is a supplementary line, not the headline payout. If you've seen pyramid-style affiliate programs before, this is structurally different: there's no downline beyond the one recruiting level, no recruiter-of-recruiter income, no compounding tiers. It's typically treated as a finder's fee (reportable on a 1099-NEC like any other commission) with a fixed depth, modeled in the database and enforced in the backend. ## Setup (brand side) Recruiting overrides are configured **per campaign**, not org-wide. Different campaigns can have different override percentages, or you can leave overrides off entirely on some campaigns. 1. Open `/dashboard/campaigns/[id]` for the campaign you want to enable. 2. Open the **Affiliates** tab and scroll to the **Recruiting** section. 3. Toggle **Enable sub-affiliate recruiting** on. 4. Set **Override percentage (0-100)**, measured as a percent of the recruit's commission. Typical values are 5 to 15 percent. 5. Save. ![The Recruiting section on the Affiliates tab, with the recruiting toggle and override percent](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/sub-affiliates-tab.png?v=5) The override applies to new commissions only, never retroactively. Sales that landed before you turned the toggle on do not generate overrides after the fact. **Plan tier:** included on every plan. (The affiliate-side recruitment link is unrestricted: if you turn this on, every approved affiliate in that campaign gets one automatically.) **Permissions:** Owner role on the org. The change is audit-logged as `program.sub_affiliate_config_changed`. ## What the math looks like Imagine: - Campaign override percent: **10%** - B's commission rate on the campaign: **20%** - Customer makes a $100 sale through B's tracking link. What happens: 1. B's commission row is created: $100 sale, $20 commission (20% of $100). 2. An override row is created for A: $0 sale amount, $2 commission (10% of B's $20). A's override is computed against **B's commission**, not against the underlying sale. If you raise the override percent, A earns more from the same B sale. If you lower B's commission rate, A's override drops proportionally even though the sale amount didn't change. ## Affiliate experience Every affiliate has a "Recruit" item in the left nav at `/a/recruit`. It is a hub with a campaign picker (only campaigns with recruiting enabled show up in it), the override terms, and the affiliate's personal link. A Recruit section also appears on the campaign detail page when recruiting is enabled for that campaign. ![The affiliate-side recruit hub with the campaign picker, override terms, and personal link](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/affiliate-recruit-hub.png?v=3) Approved affiliates get: - A personal recruitment link in the form `https://{yourbrand}.rekomi.com/{campaignSlug}?recruited_by={token}` (or your own domain, if you serve the public landing page there). The token is slug-first: it uses the affiliate's account-level `?via=` slug when they have one (for example `?recruited_by=v6media`), falls back to their per-campaign link slug, and only uses the raw affiliate id when neither slug exists. - A live count of who they've recruited and how much override they've earned. - A copy-link button for sharing on social. When a recruit (B) lands on the public apply page through that link, the token is forwarded as `recruitedBy` in the form submission and resolved to the recruiter on the new affiliate row. Once B is approved by you (or auto-approved by the campaign), every sale B drives produces both B's commission row and A's override row. B's commission is recorded first; A's override is written as a separate follow-on step, so a problem with the override can never block or roll back B's commission. If the override step fails, it is backfilled automatically when the source event is redelivered, and override creation is safe to retry, so redelivery never produces a duplicate. ## Refund behavior If a parent conversion (B's sale) is refunded in full, the override (A's row) is reversed in full at the same time. If the parent conversion is partially refunded (only some of the sale amount comes back), the override is reduced **proportionally**. Example: - Original B commission: $20 on a $100 sale. - Original A override: $2. - Customer is refunded $40 (40% of the sale). - B's commission is clawed back by 40% ($8 reversed, $12 remaining). - A's override is clawed back by 40% ($0.80 reversed, $1.20 remaining). Both refund cascades trigger `conversion.refunded` webhooks for any subscribed endpoints. ## Caveats - **Both sides must be Approved when the sale lands.** While B is in `Pending` or `Rejected` status, B's tracked clicks are still recorded for analytics but no commission rows fire, and no override fires for A. The same check applies to the recruiter: if A is not in `Approved` status at the moment B's sale lands, no override is created for that sale. - **Recruitment is one-directional and immutable.** Once A recruits B and B is approved, the link cannot be transferred to a different recruiter, even by you as the brand. This is a deliberate audit-trail decision; if you need to fix a mis-recruit, contact support. - **Self-recruit is silently dropped.** An affiliate cannot recruit themselves, and the check is broader than the affiliate id: if the applicant is the same Clerk account as the recruiter, or applies with the same email address, the application still goes through but the recruiter tag is silently dropped. The applicant becomes a normal affiliate with no recruiter attached. - **Recruiter and recruit share the campaign.** The recruitment link points at one campaign's apply page, and the recruiter must be an Approved affiliate in that same campaign for the link to bind. The override percent used is that campaign's. - **Toggling off stops new overrides immediately.** If you disable recruiting on a campaign, no new override rows are created from that moment on, even for existing recruits; override amounts already accrued are untouched. Recruiter-recruit relationships are kept, so re-enabling the toggle resumes overrides for future sales. While the toggle is off, applications through old recruitment links still succeed but no recruiter is attached. On the affiliate side, the `/a/recruit` nav item stays visible; what disappears is the campaign in the recruit hub's picker and the Recruit section on the campaign page. ## Tax handling Override income IS taxable to the recruiter (A). It counts toward Rekomi's 1099-NEC USD candidate threshold ($600/year in USD-equivalent for U.S. affiliates, deliberately below the statutory $2,000 that applies to 2026 payments). If A earns $580 from their own sales and $100 in overrides in a calendar year, that's $680 total and crosses the threshold. Rekomi's tax-form flow already aggregates both lines for you. See [Tax forms](/docs/brands/tax-forms) for the full compliance flow. ## Plan tier | Surface | Required tier | |---|---| | Enable recruiting on a campaign | Every plan | | Set override percent | Every plan | | Affiliate sharing their recruitment link | Unrestricted (Free for the affiliate, regardless of your tier) | | Override rows fire on sales | Unrestricted once the campaign is enabled | Affiliates never pay anything. Recruiting is included on the brand side, on every plan, no gate on the toggle. ## FAQ **Can I run this without telling my existing affiliates?** Technically yes. The `/a/recruit` page is always in the affiliate nav, and enabling recruiting on a campaign makes it appear in that page's campaign picker automatically, so you can flip it on and existing affiliates will discover it. In practice, send a one-line broadcast: "We just turned on a 10% recruit-and-earn bonus. Share your link from /a/recruit." Adoption is much higher when affiliates know it exists. **Can I set different override percents per affiliate?** Not today. The override percent is per-campaign. If you want a tiered structure, create separate campaigns with different override percents and route VIP affiliates into the campaign with the higher rate. **What if I want a deeper, multi-level program later?** You can't enable a deeper chain through configuration. It's a hard cap in the schema. Rekomi's recruiting feature is 2-tier by design: you, plus one recruiting level. It never becomes 3-tier or N-tier downlines. If your business requires a true multi-level model down the line, that's a feature request, not a setting flip. Open a support ticket and we'll talk through whether it's worth the regulatory complexity. **Will my affiliates see who recruited whom?** Affiliates only see their own downline. They cannot see who recruited them, and they cannot see anyone else's downline. The brand-side admin (you) can see the full graph. **Does this work with coupon-code attribution?** Yes. If B's sale is attributed through a coupon (rather than a click on a referral link), the override still fires for A as long as B is the affiliate who owns the coupon. The attribution path of the parent sale doesn't change anything about how the override is computed. **Does this work with pixel tracking and S2S?** Yes. Override rows fire on every Rekomi-attributed conversion regardless of which ingestion path produced the parent commission (Stripe webhooks, pixel, S2S). ## See also - [Coupon-code attribution](/docs/brands/coupons): the other alternative to link-only attribution. - [Manage affiliates](/docs/brands/manage-affiliates): approval queue, affiliate lifecycle, override income shows up in the affiliate detail view. - [Sub-affiliate API](/docs/developers/sub-affiliate): endpoint reference + webhook payload shape for override conversions. --- # Tax forms Source: https://rekomi.com/docs/brands/tax-forms > The affiliates near the US tax threshold view, the conservative $600 tracking threshold, and the year-end CSV export. --- title: Tax forms description: The affiliates near the US tax threshold view, the conservative $600 tracking threshold, and the year-end CSV export. --- When you pay U.S. affiliates through Rekomi, **Rekomi (Uplup Inc.) is the payer of record**. Year-end 1099-NEC filing is handled through Stripe under Rekomi's name; you do not file anything for your affiliates. What this page covers is the visibility you get on top of that: Rekomi watches each affiliate's year-to-date earnings, flags everyone who could plausibly need a 1099-NEC, and gives you a candidates CSV for your own records and your accountant. How the underlying tax form is collected depends on the affiliate's payout method. Affiliates paid on **Stripe Express** provide their tax details to Stripe during Express onboarding (a W-9 for U.S. persons), and Stripe holds the form. Affiliates paid via **PayPal** (countries Stripe Express does not cover) upload their W-8 directly to Rekomi; their payouts are held until a current form is on file. ## The Affiliates near the US tax threshold view `/dashboard/payouts/tax` shows every affiliate whose year-to-date earnings, or payments (valued at the USD amount actually paid, the rate locked on each payout line), have crossed the $600 candidate threshold for the current tax year. The view and the CSV export cover the current tax year. Each row: - Affiliate name + email - Total earnings YTD - Tax form status (a Missing badge, or the form type on file: W9, W8BEN, W8BENE) The payout method and any tax conflicts appear in the CSV export. ![The Affiliates near the US tax threshold view listing affiliates over the threshold with year-to-date earnings and tax form status](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/tax-1099.png?v=5) The $600 threshold is deliberately conservative, not a statutory rule. The statutory 1099-NEC threshold for tax year 2026 onward is $2,000, but Rekomi surfaces candidates from $600 so nobody who might need a form slips through, and so the records hold up under older or stricter standards too. Treat the list as a heads-up: the forms themselves go out through Stripe to the affiliates who meet the statutory threshold. ## W-9 vs W-8BEN vs W-8BEN-E - **W-9**: U.S. persons and U.S. business entities. Provides the affiliate's TIN (SSN or EIN). - **W-8BEN**: non-U.S. individuals. Certifies foreign status. - **W-8BEN-E**: non-U.S. business entities. Same purpose as W-8BEN for legal entities. For Stripe-rail affiliates, Stripe collects the form during Express onboarding. PayPal and bank-deposit affiliates are prompted to pick the right W-8 on `/a/settings` and upload a signed PDF plus a canvas e-signature. ## Where tax forms are stored Forms uploaded to Rekomi (the PayPal and bank-deposit rails) live in **private** storage on DigitalOcean Spaces. (Stripe-rail forms are held by Stripe, not Rekomi.) The persisted URL is an opaque marker, never a directly-servable public URL. The file itself is opened only by the affiliate who uploaded it (`GET /api/me/tax-form`, behind their `/a/settings` page) and by Rekomi's own administrators for filing and support, each time through a short-lived pre-signed URL (5-minute TTL). Your dashboard shows the form's status, type and submission date, never the file: Rekomi is the payer of record, so the SSN or ITIN on the form never needs to reach your browser, and it never travels through a public CDN path. ## CSV export for your records From `/dashboard/payouts/tax`, click **Export CSV**. The export covers the current tax year and downloads through your signed-in dashboard session, so it needs no extra authentication step. The CSV contains one row per candidate affiliate with these columns: `affiliate_id`, `email`, `full_name`, `tax_form_type`, `tax_form_status`, `year_to_date_cents`, `year_to_date_dollars`, `rail`, `latest_tax_form_type`, `tax_conflict`, `paid_ytd_cents` How the two money columns are counted: `year_to_date_cents` is commission earned in the tax year, valued in USD at the reference rate on each conversion's date; `paid_ytd_cents` is what was actually paid during the year, at the USD amount on each payout line. A payment reversed inside the same tax year (a refund clawed back on or before December 31) does not count toward `paid_ytd_cents`. A reversal dated in a later year does not reduce it: the payment stays in the year it was made, and the later year's export footnotes the clawback in a `# NOTE` line rather than netting it against that year's payments. Rows without a USD reference rate for their date (a currency with no usable stored rate) are left out of both totals rather than guessed; the export counts those rows and names the currencies in a `# NOTE` line at the bottom of the CSV, and the same warning is flagged on the tax page above the candidates table, so the totals can understate until FX coverage is restored. Deliberately absent: TINs and addresses. The export is a records worksheet, not a filing file, so it never carries an SSN or EIN through your browser or a spreadsheet; the filing itself happens through Stripe, which holds the affiliate's tax form. A note on `full_name`: this column is the name the affiliate entered on your campaign's application form, included so the worksheet is readable. It is not the tax identity. The legal name that matters for W-9/W-8 and 1099 purposes lives on the tax form itself or the affiliate's Stripe account, both collected during payout setup, so an informal or edited signup name never affects filing. Because Rekomi is the payer of record, the 1099-NECs are filed through Stripe at year end; the CSV tells you who is likely to receive one and how much was paid to them, for your records. ## CSV formula injection guard The CSV export prefixes any field that starts with `=`, `+`, `-`, or `@` (plus tab and carriage-return prefixes) with a single quote, so spreadsheet apps do not interpret them as formulas. This is a standard defense against [CSV injection attacks](https://owasp.org/www-community/attacks/CSV_Injection) where a malicious affiliate could embed a formula in their `full_name` that runs when you open the file. Safe by default. ## Retention Rekomi keeps uploaded tax forms for the seven-year retention window that applies to payer records (26 CFR 1.6001-1). A daily cleanup deletes a form only once it is more than seven years old and no longer needed: superseded forms (a newer form exists for the same affiliate) and forms belonging to anonymized affiliates. The stored file is removed first, then the record. The most recent form of an active affiliate is never deleted, even past seven years. Erasure is the exception. When an affiliate's account is erased (a GDPR or CCPA deletion request: the affiliate deletes their own account, or you forward the request to support), their tax-form records and files are removed as part of erasure rather than waiting out the retention window. Export the 1099-NEC candidates CSV before a US affiliate you have paid is erased, so your own records stay complete. ## Affiliate-side tax form upload The affiliate's view is at `/a/settings`. See [Affiliates: Tax forms](/docs/affiliates/tax-forms) for what they see, how the canvas signature works, and what they can re-upload. --- # Team management Source: https://rekomi.com/docs/brands/team > Roles, invites, and last-owner protection. --- title: Team management description: Roles, invites, and last-owner protection. --- Multiple humans, one Rekomi organization. `/dashboard/settings/team` is where you manage who has access and what they can do. If you run Rekomi inside the [Shopify](/docs/brands/install/shopify), [Wix](/docs/brands/install/wix), [BigCommerce](/docs/brands/install/bigcommerce), or [WordPress](/docs/brands/install/wordpress) app, you can also manage your team without leaving your store admin: the app's **Settings > Team** section shows members, pending invites, and seat usage, and lets you invite, change roles, and remove members. Invites are always accepted on the web (the teammate follows the email link and signs in at app.rekomi.com), and ownership changes stay in the web dashboard. In WordPress, the Team section is available to site administrators only. ![The Team page in Settings, showing the members list with roles and the pending invites table](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/team/team-overview.png?v=4) ## Roles Four roles in a strict hierarchy: - **Owner**: full control, including the things no other role can do: deleting the organization, granting or removing the Owner role, and creating API keys and MCP tokens (which act with full access). - **Admin**: everything else. Admins connect payment gateways, run and reverse payouts, manage billing, invite and manage the team (except anything touching the Owner role), configure webhooks, and change every organization setting. If it is not account deletion, ownership, or API-key creation, an Admin can do it. - **Manager**: can manage campaigns, approve or reject affiliates, and send broadcasts. Managers can view payouts but cannot run, retry, or reverse them, and cannot change settings or webhooks. - **Viewer**: read-only access to campaigns, affiliates, and conversions; payout and audit-log pages are hidden from Viewers. The owning user when an org is created has the Owner role. There must always be at least one Owner. ## Invite a team member From `/dashboard/settings/team`, click "Invite member". Fill in: - **Email** (where the invite goes) - **Role** (Admin, Manager, or Viewer) ![The invite dialog with an email field and a role picker](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/team/team-invite-dialog.png?v=4) Rekomi generates a one-time invite link and emails it to the recipient. The accept URL looks like `https://app.rekomi.com/invites/{token}`. The page checks whether that email already has a Rekomi account and shows one primary action: **Sign in to accept** if it does, or **Create your account** if it doesn't (the other option stays available as a smaller link underneath). New teammates get a dedicated sign-up that skips the plan picker entirely, so an invited team member is never asked to pick a plan or enter a card. Once they sign in or sign up, they accept and land directly in your account as a team member. The email must match the email on the invite or the acceptance is rejected. Invites expire after 7 days. You can resend or cancel from the pending invites list. ### Team-member limits per plan Each plan includes a set number of team seats (Owner included): | Tier | Team members | |---|---| | Starter | 2 | | Growth | 5 | | Pro | Unlimited | | Enterprise | Unlimited | If you try to send an invite past your cap, or accept one when the team is already full, the API returns HTTP 402 and the dashboard prompts you to upgrade. See [Plans and trials](/docs/brands/plans-and-trials) for the full tier matrix. ## Change a role From the team list, open the actions menu (the "..." button at the end of any row) and choose "Change role". A submenu lists the four roles; pick the new one. Owners and Admins can change roles; only an Owner can grant the Owner role or change an existing Owner's role, and the last Owner cannot be demoted. The audit log records every role change. ![The row actions menu open on a member, with the Change role item and Remove action visible](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/team/team-change-role.png?v=4) ## Remove a member Open the actions menu (the "..." button) on any row and choose "Remove". Owners and Admins can remove members; only an Owner can remove another Owner, and at least one Owner must always remain (to leave as the only Owner, promote someone else to Owner first). Removal is immediate: the member's access ends, any connected-app grants for this organization are revoked, and further requests to this workspace are refused. Their data (audit log entries, prior approvals) stays for the historical record. ## Last-owner protection You cannot: - Remove the last Owner from the org. - Demote the last Owner to Admin, Manager, or Viewer. - Grant or change the Owner role unless you are an Owner (promoting another member to Owner is how ownership is shared or handed over). These rules are enforced by the server, not just hidden in the UI. A direct API call that violates them is rejected with a clear error. ## Accepting an invite The accept page at `/invites/{token}` shows the org name and the role you are being invited to, with one primary button that matches your situation: **Sign in to accept** when the invited email already has a Rekomi account, or **Create your account** when it doesn't. Signing up from an invite never involves a plan picker or a card. ![The invite accept page showing the organization, role, and invited email, with Sign in and Create account buttons](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/team/team-invite-accept.png?v=2) If you are already signed in with a different email, the page tells you which email the invite is for and offers a **Switch account** button that signs you out and takes you to sign-in with the invited email prefilled. Once you accept, you land on `/dashboard` with the new organization in context. If you belong to multiple orgs (you are an affiliate, you own one brand, and you got invited to another brand), use the org switcher in the top bar. ## Multiple organizations per user A single user can belong to many organizations with a different role in each (for example, an affiliate in one and a team member in another). Use the **brand switcher** on the right side of the top bar to flip between them (it appears once you belong to two or more brands); your avatar menu lists the same switch actions. ## Creating another organization You don't need a second Rekomi account for a second brand. Open your avatar menu and pick **New brand account** (with two or more brands, the same action also sits at the bottom of the top-bar brand switcher): it creates a separate organization under your existing login, with its own campaigns, affiliates, billing connection, subdomain, plan, and team, and switches you into it. You'll pick a plan for the new organization next; each one gets its own 14-day free trial when you do. You can own up to 10 organizations; contact support if you need more. When do you need a second organization, versus another campaign in your current one? Each organization holds exactly one payment-processor connection (one Stripe account, one Shopify store, and so on) and one brand identity (logo, name, `yourbrand.rekomi.com` portal). So: - **Same processor, same brand, different site or product:** add another campaign; each campaign has its own destination website, tracking install, and terms. - **Different processor account or a genuinely different brand:** create a new organization from the switcher. Each organization is billed separately. ## How a new member's role is set A new member starts with the role from their invite. Any Owner or Admin can change it later from the team list. There is no rule preventing you from lowering your own role beyond the last-owner protection. ## Audit trail Every team operation (invite, accept, role change, remove) is recorded in the audit log at `/dashboard/settings/audit-log`. The audit row includes who did what, when, and to whom. Useful for compliance and for debugging "who removed Sarah" questions. --- # Test your integration Source: https://rekomi.com/docs/brands/test-mode > Run a test sale through the real tracking pipeline with the seeded Test Affiliate, without paying anyone. --- title: Test your integration description: Run a test sale through the real tracking pipeline with the seeded Test Affiliate, without paying anyone. --- Before you ask a single real affiliate to promote you, you can prove the whole pipeline works: click, attribution, commission math, and your webhook, end to end. Rekomi seeds a **Test Affiliate** on your account and gives you a one-click **Run a test sale** button. ## Where to find it Open a campaign, go to the **Performance** tab, and scroll to the **Test your integration** card in the Activity section (it sits below the Recent events feed). It's one button: **Run a test sale**. ![The Test your integration card with the Run a test sale button](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/test-mode/test-integration-card.png?v=3) ## What a test sale does Clicking the button fires a pretend click and a 10-unit sale in your home currency ($10.00, or the equivalent 10 major units for currencies like JPY) from the Test Affiliate through the **real** tracking pipeline: - The click is recorded against the Test Affiliate's link, exactly the shape a real tracked click has. - The sale runs through the normal conversion engine, so the commission is computed exactly as a real sale's would be. - If you've set up a conversion webhook, it fires too; the payload's attribution method is `test` so your consumer can tell it apart. On success, the card shows the recorded commission with links to view the sale in [Conversions](/docs/brands/sales) and to open the Test Affiliate. You can run it as many times as you like; each run is a fresh event. The test never touches your payment processor. It proves Rekomi's side (click, attribution, commission math, webhook), not that your checkout stamps the affiliate onto Stripe subscriptions or invoices. To prove that half, make one small live purchase through your real checkout and refund it: see [Verify attribution before launch](/docs/brands/install/stripe#verify-attribution-before-launch). ## The Test Affiliate The Test Affiliate is a real, approved affiliate row named "Test Affiliate" with the reserved address `test@rekomi.test`. It is seeded automatically on the first campaign you create in the Rekomi dashboard; other campaigns get theirs the first time you run a test there. It exists so a test sale exercises the same code path a real affiliate's sale would. ## The money truth Test events are built so you can watch them arrive without ever owing anything: - **Never paid out.** The Test Affiliate is never a payable candidate. Even a payout run pointed directly at it computes a zero total. Nothing is ever paid out for test events. - **Excluded from aggregate stats.** Home KPIs, Reports, leaderboards, and campaign stat cards all filter test activity out, so your real numbers stay honest. This applies to sales made with the Run a test sale button. A live purchase you attribute to the Test Affiliate to verify your checkout is a real sale and stays in Reports until you refund it. - **Excluded from emails and broadcasts.** No automated email is ever sent for test activity (the reserved `@rekomi.test` address can't receive mail), and the Test Affiliate is never counted in a [broadcast](/docs/brands/broadcasts) audience. - **Still visible in ledgers.** Test rows stay on the [Conversions page](/docs/brands/sales) and in the campaign's Recent events feed on purpose, so you can see your test land. One deliberate refusal: manual sale entry rejects the Test Affiliate. A manually entered sale is real money, and real money on a test identity would dodge the reporting exclusions while never being payable. If you want to exercise the pipeline, use the test button instead. ## When it fails The test returns an error instead of recording anything if the campaign is suspended. The button is also rate limited (at most 10 runs per minute), so a stuck retry loop can't flood your event feed. --- # Tracking and attribution Source: https://rekomi.com/docs/brands/tracking-and-attribution > Cookie windows, first-touch vs last-touch, commission tracking, and what counts as a sale. --- title: Tracking and attribution description: Cookie windows, first-touch vs last-touch, commission tracking, and what counts as a sale. --- This page covers how Rekomi decides which affiliate gets credit for a sale, when that credit becomes payable, and how refunds reverse it. ## How tracking works today Every affiliate has one referral slug (shared across all the campaigns they join). They append it to links to your site as `?via={slug}`, for example `https://yourbrand.com/?via=abc123`. When the visitor lands with that parameter, the tracking script (or the S2S API) records the click and attributes it to that affiliate. Attribution happens three ways: the install snippet on your site reads `?via={slug}` and sets a first-party cookie; your backend calls the [S2S tracking API](/docs/developers/s2s-tracking) telling Rekomi "this customer was referred by slug X"; or, on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel, the platform connection under Integrations reads the tag the platform stored on the customer at signup (Rekomi's links carry it as `utm_source=rekomi&utm_campaign={slug}`) when a sale arrives with no affiliate named any other way. Whichever way, the same affiliate gets credited. ![The Attribution settings page listing the 21 recognized affiliate-link params and custom param configuration](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/attribution-settings.png?v=5) ## Cookie window `cookieWindowDays` on a campaign defines how long an affiliate's attribution survives after the initial touch. Default is 60 days. Typical ranges: - **30 days**: short-cycle products with quick conversion (impulse buys, free-to-paid signups). - **60 days**: medium consideration (most SaaS). - **90 days**: longer enterprise sales cycles, high-ticket courses. - **180 days**: very long consideration (annual-only products, enterprise). The longer the window, the more refund-window exposure you carry on the commissions it credits. ## Attribution model `attributionModel` controls which affiliate wins when multiple have referred the same visitor within the window. - **FirstTouch**: the affiliate who drove the *first* tracked click within the window gets the credit. Rewards the affiliate who introduced the product. - **LastTouch** (default): the affiliate who drove the *most recent* tracked click before the sale gets the credit. Rewards the affiliate who closed the deal. The default is LastTouch, which credits the affiliate who drove the final click before purchase. Switch a campaign to FirstTouch when you want to reward the affiliate who first introduced the customer, which some subscription brands prefer. ## What counts as a sale A sale is any Stripe event that produces a commission. The most common is a paid invoice (`invoice.paid`). For per-sale campaigns set to a recurring revenue share, every paid invoice produces a sale. If you set `recurringDelayInvoices`, the first N renewal invoices earn no commission at all; commissions start after the delay window. Sales move through statuses: 1. **Pending**: invoice paid, in the refund hold window (an organization-level setting, default 30 days). Once the window passes, the commission is payout-eligible; the sale stays Pending, there is no automatic status flip. 2. **Approved**: manually approved. Both Approved sales and Pending sales past the hold window are eligible for payout. 3. **Paid**: included in a payout batch and transferred to the affiliate's Stripe Express account. 4. **Refunded**: Stripe refunded the customer; the commission is reversed. 5. **Invalidated**: quarantined as invalid traffic, automatically for a flagged click or lead, or by hand on any row still in grace. Release it from the Conversions page if it was a false positive. 6. **Denied**: a denied sale (rare; usually only for disputed sales). In the dashboard, your manual actions on a conversion are **validate** and **invalidate**, available from the Conversions page. ## Free signups and leads Not every signup is a paid sale on day one. A **lead** is a free, identified signup tied to the affiliate who referred it, recorded before any payment. It does two things for you: - You can see which affiliate drove a free signup, not just paid ones, so your funnel reads visitor, then free signup, then sale. - When that free user later upgrades to a paid plan, the sale is credited back to the same affiliate by matching the email, even if the click cookie was cleared, blocked, or expired in the meantime. Leads stay matchable for 90 days. A signup lead never pays a commission on its own; the commission still happens when the paid sale comes through (a qualified lead on a campaign that pays per lead is a separate row with its own commission, see [Pay per click or lead](/docs/brands/cpc-and-cpl)). The lead is just the durable link between the free signup and the eventual upgrade. **For processors that send a customer or subscription webhook before payment (Stripe, Paddle, Recurly, Chargebee, Creem), leads are captured automatically.** Lead tracking needs no setup on your side, so subscription signups, free trials, and fully discounted (100%-off coupon) signups all become leads straight from the processor webhooks you already receive, with no extra code on your site. **Free-plan and freemium signups need one line of code.** If signing up for your free tier does not create a Stripe or Paddle subscription or customer, there is no processor event to turn into a lead, so your signup code calls `Rekomi.convert({ email })` at account creation. Pass the same email the customer will bill with; that is what lets a later paid upgrade match back to the lead and credit the affiliate. The same one-line call covers Braintree and Shopify, which have no pre-payment webhook. For the exact code, the email-match details, and the server-to-server option, see [Track leads and signups](/docs/developers/lead-tracking). ## Refund-aware attribution When Stripe sends a `charge.refunded` webhook, Rekomi: 1. Claws back the commission proportionally to the refund. A 40% refund on a $100 sale with a $20 commission reverses $8, not the full $20. 2. Repeats this for every partial refund on that sale, each one clawing back its share, until the refunds add up to the full sale amount. 3. Flips the sale's `Status = Refunded` only once cumulative refunds cover the entire original amount. Until then the sale keeps its existing status and the remaining commission stays payable. 4. If a clawback lands after the commission was already paid out (rare with the default 30-day hold), the refunded share is owed back and the affiliate's next payout deducts it automatically; you can also reverse that commission on Stripe from the payout's Detail page (Payouts > Paid) right away, and the two never double-count. See [Refunds after a payout](/docs/brands/payouts#refunds-after-a-payout). One exception: for Gumroad, a partial refund does not trigger an automatic clawback (a full refund still does). A subscription cancellation (`customer.subscription.deleted`) works differently: Rekomi records a cancellation marker and future renewal invoices stop, so no new commissions accrue, but it does not reverse commissions already earned. Only refunds and chargebacks reverse commission. This happens automatically. You do not have a "mark as refunded" button in the dashboard today because Stripe is the source of truth. ## S2S tracking for non-Stripe events If you have sales that do not flow through Stripe (mobile app purchases, server-side trials, custom events), use the [S2S tracking API](/docs/developers/s2s-tracking). It accepts a POST with an `externalEventId` for idempotency, an `affiliateSlug`, an `amountCents`, and a `currency`. It is HMAC-signed and de-duped per organization. --- # White-label Source: https://rekomi.com/docs/brands/white-label > Remove Rekomi branding from your affiliates' dashboard, emails, and invite pages. --- title: White-label description: Remove Rekomi branding from your affiliates' dashboard, emails, and invite pages. --- White-label puts your brand where Rekomi's would normally be, across your affiliates' entire experience. It is available on the **Pro** and **Enterprise** plans and is one switch: **Settings, Account profile, White-label**. ## What changes when it's on For any affiliate whose only campaigns are yours: - **Their dashboard** shows your logo (or your brand name) in place of the Rekomi wordmark, uses your brand color for accents, and drops "Rekomi" from browser tab titles. - **Marketplace surfaces are hidden** from their navigation: no creator network, no open campaign directory, no deal marketplace. Their dashboard reads as your program, not a marketplace they might wander out of. - **Emails to your affiliates** (application updates, commission notifications, payout emails, invites, broadcasts) drop the "via Rekomi" attribution and the Rekomi footer links. Pair this with a [custom sending domain](/docs/brands/sending-domain) so the sender address is also yours. - **Invite pages** lose the "Powered by Rekomi" footer. - Your **signup pages** already support removing "Powered by Rekomi" (Pro, per campaign, in the landing page builder) and can run on [your own domain](/docs/developers/custom-tracking-domain) on any paid plan. Upload a logo and set a brand color in **Settings, Account profile** first; those are what your affiliates will see. ## The one honest boundary An affiliate account on Rekomi can join campaigns from more than one brand. Your white-label chrome applies when your campaigns are the only ones an affiliate belongs to, which is the case for virtually all recruited ambassadors. If one of your affiliates independently joins another brand's campaign later, their dashboard returns to the neutral view (showing one brand's identity over another's campaigns would be wrong). Two things intentionally keep their existing identity: - **Sign-in** on app.rekomi.com happens on the standard hosted page (anti-phishing design). On your own portal domain, sign-in uses an email code sent to the affiliate - see the next section. - **Payout and tax identity**: payout onboarding runs through Rekomi's payment infrastructure (Stripe Express collects the W-9/W-8; on the PayPal and bank-deposit rails the affiliate uploads it to Rekomi), so affiliates see that identity during money setup. Rekomi is the [payer of record](/docs/brands/tax-forms) for 1099 purposes: it disburses the payouts, collects the forms, and handles the year-end 1099-NECs, with a candidates worksheet for your own records. ## Sign-in and dashboard on your own domain With white-label on, your affiliates can also sign in and manage your campaigns directly ON your portal domain - `yourhandle.rekomi.com` or your own custom domain - without ever visiting rekomi.com: - **Sign-in** lives at `/login` on your portal domain. Affiliates enter their email and receive a 6-digit code (no password, no Rekomi account screens). Approval emails deep-link straight into this dashboard. - **The dashboard there is scoped to your brand**: your campaigns only, with the affiliate's links (including the one-time link handle customization), coupon codes, recent activity, and your uploaded creatives - all under your logo and brand color. - **One account underneath**: it's the same affiliate identity as app.rekomi.com. An affiliate can use either door; earnings and links are identical in both. - **Money stays on the secure app domain**: connecting a payout account and tax forms open app.rekomi.com, where the payment infrastructure identity lives. Nothing to configure beyond the white-label switch: once it's on, `/login` works on your portal domain and approval emails route there automatically. Your affiliates' sign-in codes are sent from your configured [sending domain](/docs/brands/sending-domain) with your branding. The sign-in URL is shown on the white-label card in Settings - share that link in your affiliate onboarding so people know where their door is. One honest caveat: the branded sign-in exists only while white-label is active. If you turn the switch off or move off Pro, `/login` on your domain stops working and previously sent approval links stop resolving - affiliates keep their accounts and can always sign in at app.rekomi.com (the dead page points them there), but tell your affiliates before you flip it off. ## Enabling it 1. Go to **Settings, Account profile**. 2. Confirm your logo (and optionally brand color) are set in the branding section. 3. Turn on the **White-label** switch below it. The change applies immediately: dashboards on next load, emails from the next send. Turning it off restores the standard experience just as quickly. Enabling requires Pro or higher; turning it off is always available. --- # Authentication Source: https://rekomi.com/docs/developers/authentication > Bearer API keys, scopes, signing secrets, rotation, and revocation. --- title: Authentication description: Bearer API keys, scopes, signing secrets, rotation, and revocation. --- Every Rekomi API call needs a bearer key. Some calls (S2S tracking, webhook delivery) need an additional HMAC signature with a separate signing secret. This page covers both. ## Generate an API key Create your key from the dashboard: go to `/dashboard/settings/api-keys` and click "Create key". ![Rekomi dashboard API keys settings, showing the Create key button and the list of existing keys with their prefix, last-used timestamp, and status.](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-api-keys.png?v=4) Only the workspace **Owner** can create, rotate, or revoke API keys. Creating and rotating a key require a Starter or higher plan; revoking works on any plan (so a downgraded brand can still clean up). Required: - **Name**: human-readable label like "Vercel production" or "data warehouse sync". - **Scope**: choose **Read** (GETs only) and/or **Write** (create, update, delete). Click Create. Two values appear in a modal, shown EXACTLY ONCE: - **Plaintext bearer**: starts with `rk_live_`. 32 random bytes, base64url-encoded. Copy now. - **Plaintext signing secret**: starts with `rks_`. 32 random bytes. Used for HMAC on S2S endpoints. Copy now. After you close the modal, the plaintext values are unrecoverable. The bearer is stored as a SHA-256 hash on our side. The signing secret is encrypted at rest. If you lose your keys, rotate (below) or revoke and create new ones. The entire key lifecycle (create, rotate, revoke) requires an interactive dashboard session: an existing API key or OAuth token can never mint, rotate, or revoke keys (`403 interactive_session_required`). The same applies to MCP connection tokens. A leaked credential therefore cannot escalate into fresh credentials. ## Use the bearer Set the `Authorization` header on every request: ```http GET /api/v1/programs Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxx ``` The bearer identifies your organization. We do not require a separate `X-Org-ID` header; the bearer encodes everything we need. ## Scopes A key's `scopes` is an array containing `"read"` and/or `"write"`: - **read**: GET endpoints only. Cannot create, update, or delete resources. Cannot rotate keys. - **write**: create, update, and delete access on POST, PATCH, DELETE. Subject to per-endpoint role checks (some endpoints further require Owner or Admin role). A key without `write` cannot mutate anything. Neither scope can trigger payouts: running or retrying a payout requires a signed-in Owner dashboard session and always returns 403 `interactive_session_required` for API keys. The literal `"read_write"` is also accepted and means the same as `"write"`. Pick a read-only key for any third-party tool you do not fully trust (analytics dashboards, BI tools, ETL jobs that only need to copy data out). Grant write for your own application code. ## Rotate a key Rotating swaps a key's bearer and signing secret without changing its name or scopes. Click Rotate next to any key. A new plaintext bearer and signing secret are shown EXACTLY ONCE. The old values stop working immediately. There is no grace period, so rotate during a maintenance window if you have many integrations to update. Use rotation when: - A team member with key access leaves. - A key is accidentally committed to a public repo. - Quarterly rotation as part of your security policy. ## Revoke a key Revoking permanently disables a key. Click Revoke. The key stops working immediately, and this cannot be undone. If you need access back, create a new key. ## Audit trail Every key change is tracked automatically, so you can always see who did what. Creation, rotation, and revocation are all logged in the audit log at `/dashboard/settings/audit-log`. The audit row includes the team member who did it. Creation rows include the key name and prefix (not the plaintext); rotation and revocation rows reference the key's id. ## Where keys are visible The list at `/dashboard/settings/api-keys` and `GET /api/api-keys` (dashboard-session only; this endpoint requires a browser sign-in session and cannot be called with an API key) shows each key as: - `id` - `name` - `keyPrefix` (first 16 characters of the bearer; safe to log) - `scopes` (array containing `"read"` and/or `"write"`) - `lastUsedAt` (timestamp, null if the key has never authenticated) - `expiresAt` (null if non-expiring) - `revokedAt` (null if active) - `createdAt` The plaintext bearer is never recoverable after creation. We also do **not** return the IP a key was last used from on the API or dashboard list; IPs land in the audit log instead, where they're scoped to JWT Owner dashboard callers only and are stripped from API-key callers (see [Reference](/docs/developers/reference#audit-log)). ## Signing secret The signing secret is used for HMAC-SHA256 signatures on S2S endpoints. Separate from the bearer because: - The bearer authenticates "who is making this request". - The signing secret authenticates "the body of this request was not tampered with in transit". Some integrations need only the bearer (most CRUD endpoints). S2S tracking requires both. See [S2S tracking](/docs/developers/s2s-tracking) for the signature format. ## Webhook signing secret Webhook **outbound** endpoints (the ones Rekomi POSTs events to) have their own signing secrets, separate from the API key signing secrets above. When you create a webhook endpoint at `/dashboard/settings/webhooks`, you get a one-time-shown signing secret for verifying inbound payloads on your server. The two secrets do not interchange. Use the right one for the right direction. ## Best practices - Store keys in a secrets manager (1Password, AWS Secrets Manager, HashiCorp Vault). Never commit to source control. We run gitleaks scans on our own repos; you should too. - Use separate keys per environment (one for prod, one for staging). Use scopes (one `read` for monitoring, one `read_write` for app code). - Rotate quarterly even without incidents. ## What if you lose a key Rotate from the dashboard. If you cannot access the dashboard (account compromise), chat with us from any rekomi.com page (the messenger is in the bottom right, no login required) and we can revoke server-side. --- # Conversion currency Source: https://rekomi.com/docs/developers/conversion-currency > ISO 4217 allowlist, error codes, Stripe normalization, and the home-currency settings endpoint. --- title: Conversion currency description: ISO 4217 allowlist, error codes, Stripe normalization, and the home-currency settings endpoint. --- Every conversion ingested by Rekomi carries a `currency` field. The full list of supported codes is below, plus the error response shape when an unsupported code is sent and the developer-side surface for reading or changing the org's home (display) currency. For the brand-facing concept page (when to change HomeCurrency, how FX normalization works, how payouts settle in USD, how the 1099-NEC handles non-USD earnings), see [Multi-currency](/docs/brands/multi-currency). ## Supported currencies Rekomi supports 135+ ISO 4217 codes, validated server-side on every ingest path: every currency Stripe can present a price in, plus the three-decimal Gulf currencies (BHD, JOD, KWD, OMR, TND) common on other gateways. ``` AED AFN ALL AMD AOA ARS AUD AWG AZN BAM BBD BDT BHD BIF BMD BND BOB BRL BSD BWP BYN BZD CAD CDF CHF CLP CNY COP CRC CVE CZK DJF DKK DOP DZD EGP ETB EUR FJD FKP GBP GEL GIP GMD GNF GTQ GYD HKD HNL HTG HUF IDR ILS INR ISK JMD JOD JPY KES KGS KHR KMF KRW KWD KYD KZT LAK LBP LKR LRD LSL MAD MDL MGA MKD MMK MNT MOP MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SEK SGD SHP SLE SOS SRD SZL THB TJS TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VND VUV WST XAF XCD XCG XOF XPF YER ZAR ZMW ``` Codes are normalized to **uppercase** server-side, so `usd` and `USD` are equivalent on the wire. One transitional alias: amounts arriving as `ANG` (the retiring Netherlands Antillean guilder) are recorded as `XCG`, its one-to-one successor. Anything else (crypto tickers, currency-substitute tokens like `XBT`, retired codes) is rejected. ## Ingest paths that carry a currency Conversions carry a currency on two ingest paths: the server-to-server postback and the payment-processor webhooks. ### `POST /api/tracking/s2s` Server-to-server, HMAC-signed. See [S2S tracking](/docs/developers/s2s-tracking) for auth + signing. ```json { "externalEventId": "purchase_abc123", "affiliateSlug": "jane-recommends", "amountCents": 9900, "currency": "EUR", "customerId": "cus_external_xyz" } ``` `currency` is optional. Omit to default to `"USD"`. ## Currency handling on the S2S postback On the postback, an **absent or blank** `currency` defaults to USD. Any other value must be a supported 3-letter code: a malformed value such as `EURO` or `US$`, and any 3-character value **outside the supported list** (including ones with digits like `US1`), is rejected with the same error (the trimmed, uppercased value you sent is echoed back). Since 2026-09-15 a malformed value is never silently treated as USD, so a typo in your integration surfaces as a 400 instead of recording the sale in the wrong currency: ```http HTTP/1.1 400 Bad Request Content-Type: application/json ``` ```json { "error": "unsupported_currency", "currency": "XYZ" } ``` `unsupported_currency` means either "this is not a 3-letter code" or "we recognize the code, we just do not carry an FX rate for it." Handle it by sending a supported code from your side, or contact support if you need the allowlist extended. The trimmed, uppercased value you sent is echoed back in `currency`. ## Curl examples ### USD conversion (S2S) ```bash BODY='{"externalEventId":"order_001","affiliateSlug":"jane","amountCents":9900,"currency":"USD"}' T=$(date +%s) SIG=$(printf "%s.%s" "$T" "$BODY" | openssl dgst -sha256 -hmac "$REKOMI_SIGNING_SECRET" -hex | awk '{print $2}') curl -sS https://api.rekomi.com/api/tracking/s2s \ -H "Authorization: Bearer $REKOMI_API_KEY" \ -H "X-Rekomi-Signature: t=$T,sig=$SIG" \ -H "Content-Type: application/json" \ -d "$BODY" ``` ### EUR conversion (S2S) ```bash BODY='{"externalEventId":"order_002","affiliateSlug":"jane","amountCents":8900,"currency":"EUR"}' T=$(date +%s) SIG=$(printf "%s.%s" "$T" "$BODY" | openssl dgst -sha256 -hmac "$REKOMI_SIGNING_SECRET" -hex | awk '{print $2}') curl -sS https://api.rekomi.com/api/tracking/s2s \ -H "Authorization: Bearer $REKOMI_API_KEY" \ -H "X-Rekomi-Signature: t=$T,sig=$SIG" \ -H "Content-Type: application/json" \ -d "$BODY" ``` ### JPY conversion (S2S) JPY has no minor unit. `amountCents` is still required, but Rekomi treats the integer as whole yen on display. A 12,000 JPY sale is `"amountCents": 12000`. ```bash BODY='{"externalEventId":"order_003","affiliateSlug":"jane","amountCents":12000,"currency":"JPY"}' T=$(date +%s) SIG=$(printf "%s.%s" "$T" "$BODY" | openssl dgst -sha256 -hmac "$REKOMI_SIGNING_SECRET" -hex | awk '{print $2}') curl -sS https://api.rekomi.com/api/tracking/s2s \ -H "Authorization: Bearer $REKOMI_API_KEY" \ -H "X-Rekomi-Signature: t=$T,sig=$SIG" \ -H "Content-Type: application/json" \ -d "$BODY" ``` ## Stripe-webhook currency handling For brands using the Stripe integration, conversion currency comes from the `currency` field on the Stripe subscription-created and invoice-paid events. Stripe lowercases everything (`"usd"`, `"eur"`). Rekomi: 1. Uppercases the code (`"usd"` → `"USD"`). 2. Checks it against the same supported allowlist used by the S2S postback. 3. If supported, stores it on the conversion row. 4. If unsupported (now very rare: the allowlist covers every Stripe presentment currency), the conversion is still recorded so no revenue is lost, and an internal alert fires with the offending code; the balance becomes payable once Rekomi extends the allowlist and rates exist for the code. Reference rates come from two mid-market sources: the ECB (via Frankfurter) for the majors and Xe for every other supported code. This means: **Stripe brands cannot accidentally drop conversions by selling in a non-allowlisted currency.** The S2S postback is stricter because the caller controls the field and can validate before sending. ## Reading / updating the home currency Your application can read or set the org's `HomeCurrency` through a settings endpoint, using the same `Authorization: Bearer rk_live_*` auth as the rest of the API. ### `GET /api/v1/settings/home-currency` ```json { "homeCurrency": "USD", "changedAt": null, "canChange": true, "supportedCurrencies": [ "AED", "AFN", "ALL", "AMD", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLE", "SOS", "SRD", "SZL", "THB", "TJS", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST", "XAF", "XCD", "XCG", "XOF", "XPF", "YER", "ZAR", "ZMW" ] } ``` - `homeCurrency`: current setting. `"USD"` for any org that never explicitly changed it. - `changedAt`: ISO-8601 timestamp of the most recent change, or `null` if never changed. - `canChange`: `true` when your plan includes home-currency changes (Pro tier). `false` otherwise. Trials grant the features of the trialed tier only, with no separate trial bypass. Use this to gate UI without making a failing PUT. - `supportedCurrencies`: the full supported allowlist (135+ codes), returned in alphabetical order. Provided so clients don't have to hard-code the list. ### `PUT /api/v1/settings/home-currency` ```json { "homeCurrency": "EUR" } ``` Requires: - The Admin role or above on the org (Manager / Viewer return 403). - A verified email on the calling account. - A plan tier that includes home-currency changes (Pro tier). A trial grants the features of the trialed tier only; it is not a bypass. - For API-key callers, a key with read and write access. - A supported currency code. Response on success: ```json { "homeCurrency": "EUR", "changed": true, "changedAt": "2026-05-18T14:22:01Z" } ``` `changed` is `false` when the value was already set (an idempotent repeat). Error responses: | HTTP | `error` | Cause | |---|---|---| | 400 | `unsupported_currency` | Code not in the supported allowlist. The response includes a `supplied` field with the code you sent. | | 402 | `plan_tier_required` | Plan tier does not include home-currency changes | | 403 | `insufficient_role` | Caller is below the Admin role. The body includes `required` ("Admin") and `actual` (the caller's role). | Unsupported-currency shape on this settings PUT: ```json { "error": "unsupported_currency", "supplied": "XYZ" } ``` Note this differs from the S2S postback, which returns the offending code under a `currency` key (uppercased) rather than `supplied`. PUTs are idempotent: setting the same currency twice returns `200` both times, and only the first call writes a `changedAt` + audit-log entry. ## See also - [Multi-currency for brands](/docs/brands/multi-currency): concept page, payout splitting, tax-form handling. - [S2S tracking](/docs/developers/s2s-tracking): server-side ingestion + refund endpoint. - [Webhooks](/docs/developers/webhooks): `payout.created` / `payout.paid` payloads always carry the per-payout `currency`. - [Sub-affiliate API](/docs/developers/sub-affiliate): override conversion rows carry the same `currency` field as the parent sale. --- # Coupon code tracking Source: https://rekomi.com/docs/developers/coupons > Create per-affiliate Stripe promotion codes, attribute conversions on redemption, and clawback refunds automatically. --- title: Coupon code tracking description: Create per-affiliate Stripe promotion codes, attribute conversions on redemption, and clawback refunds automatically. --- Rekomi mints coupons + promotion codes on your behalf and credits the right affiliate when a customer redeems one at checkout. No click cookie required, no tracking script. Use this when your buyers will type a code rather than click a link (influencer programs, podcast spots, offline ads). On a Stripe workspace the codes are minted as Stripe coupons and promotion codes; on a Shopify-connected workspace the per-affiliate codes are minted directly in Shopify as discount codes instead. **Plan tier required: Starter or higher** for direct `rk_live_*` API access. Dashboard sessions work on every paid plan. ## Concepts - **Campaign coupon**: the discount semantics. Maps 1:1 to a Stripe `coupon`. You set the percent or amount, duration, max redemptions, expiry. Once created, the discount is immutable (Stripe rule). To change a discount, archive the parent and create a new one. - **Affiliate coupon**: a redeemable code attached to one affiliate. Maps 1:1 to a Stripe `promotion_code`. Multiple per affiliate allowed (vanity variants like `SARAH20`, `SARAHSUMMER`). Attribution: when a customer redeems a promotion code at checkout, Rekomi's Stripe webhook sees `invoice.discounts[].promotion_code` and credits the affiliate who owns it. If `overridesClickAttribution` is on (recommended, and what the dashboard pre-selects), the coupon wins even when there's a click cookie. Refunds reverse commission, and once the order is fully refunded they mark the conversion refunded and decrement the redemption count. ## Authentication All endpoints accept either a dashboard JWT or a public-API bearer: ``` Authorization: Bearer rk_live_xxx ``` Get a key in `/dashboard/settings/api-keys`. Reads require `read` scope; writes require `read_write`. ## Create a campaign coupon This sets up the discount rules that per-affiliate codes will inherit. ```http POST /api/v1/campaigns/{programId}/coupons ``` Request body: ```json { "name": "Summer 20% off", "discountType": "PercentOff", "discountValue": 20, "discountCurrency": null, "duration": "Once", "durationInMonths": null, "maxRedemptionsPerCode": 100, "expiresAt": "2026-12-31T23:59:59Z", "selfMintMode": "Disabled", "grantedAffiliateIds": [], "maxCodesPerAffiliate": 0, "overridesClickAttribution": true, "productRefs": [] } ``` Validation: - `discountType` is `PercentOff` (`discountValue` 1-100) or `AmountOff` (`discountValue` positive minor units of `discountCurrency`, so cents for USD and whole yen for JPY; `discountCurrency` required ISO 4217). - `duration` is `Once` | `Repeating` | `Forever`. When `Repeating`, `durationInMonths` is required (1-94). - `expiresAt` must be in the future when set. - `maxRedemptionsPerCode` ≥ 1 when set. - `productRefs`: optional array of Stripe Product ids (`prod_...`) to limit the coupon to. Max 100 entries. Omit or send an empty array for a whole-order coupon (the default). Creating campaign coupons via this endpoint requires a connected Stripe rail; workspaces without one get `422 stripe_not_connected`. `productRefs` must be Stripe Product ids and only apply to Stripe-minted codes today. Self-mint fields: - `selfMintMode`: `"Disabled"` | `"AllAffiliates"` | `"SpecificAffiliates"`. Controls who may self-mint a vanity code from this coupon. Self-mint runs on the Stripe rail today: affiliates of workspaces without a mintable Stripe rail (including Shopify-connected stores) are not offered self-mint, and the mint endpoint returns `422 stripe_not_connected` there. - `grantedAffiliateIds`: the allow-list of affiliate ids, used only when `selfMintMode` is `"SpecificAffiliates"`. Removing an affiliate from the list later also revokes any codes they already minted from this coupon. - `maxCodesPerAffiliate`: positive cap on the number of active codes a single affiliate may hold from this coupon; `0` or omitted means unlimited. - `allowAffiliateSelfMint` is still accepted as a legacy alias and mirrors the mode: `true` maps to `"AllAffiliates"`, `false` to `"Disabled"`. Creating a coupon requires a live payment connection first: with no Stripe connection you get `422 stripe_not_connected`. Response: `201 Created` with the full campaign coupon record including `stripeCouponId` and `productRefs` (empty array when the coupon applies to the whole order). ```bash curl -X POST https://api.rekomi.com/api/v1/campaigns/$PROGRAM_ID/coupons \ -H "Authorization: Bearer $REKOMI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Summer 20% off", "discountType": "PercentOff", "discountValue": 20, "duration": "Once", "selfMintMode": "Disabled", "overridesClickAttribution": true }' ``` ```typescript const res = await fetch( `https://api.rekomi.com/api/v1/campaigns/${programId}/coupons`, { method: "POST", headers: { Authorization: `Bearer ${process.env.REKOMI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Summer 20% off", discountType: "PercentOff", discountValue: 20, duration: "Once", selfMintMode: "Disabled", overridesClickAttribution: true, }), }, ); const coupon = await res.json(); ``` ## List, update, archive, delete Manage existing campaign coupons with these endpoints. ```http GET /api/v1/campaigns/{programId}/coupons GET /api/v1/campaigns/{programId}/coupons/{id} GET /api/v1/campaigns/{programId}/coupons/affiliates # approved affiliates for the self-mint allow-list picker; add ?couponId= to see isGranted and mintedCodeCount per affiliate PATCH /api/v1/campaigns/{programId}/coupons/{id} # editable: name, self-mint settings, attribution toggle POST /api/v1/campaigns/{programId}/coupons/{id}/archive DELETE /api/v1/campaigns/{programId}/coupons/{id} # only when no per-affiliate codes exist ``` PATCH accepts `name`, the self-mint settings (`selfMintMode`, `grantedAffiliateIds`, and the legacy `allowAffiliateSelfMint`), `overridesClickAttribution`, and `maxCodesPerAffiliate`. Discount semantics stay immutable per Stripe's rules. Archive sets `isActive=false`, deactivates all child codes on whichever platform minted them, and marks them revoked locally. ## Generate a per-affiliate coupon This mints an individual redeemable code tied to one affiliate. ```http POST /api/v1/affiliates/{affiliateId}/coupons ``` Request body: ```json { "campaignCouponId": "c0upon01-...", "code": "SARAH20" } ``` - `campaignCouponId` is required. The campaign coupon must belong to the same program as the affiliate. - `code` is optional. When null, Rekomi generates an 8-character Crockford-style code (no `0/O`, `1/I/L` for transcribability). When set, must match `^[A-Z0-9_-]{3,32}$`. - Codes are unique per organization among active codes; a revoked code frees its string for re-minting. Duplicates return `422 code_already_taken`. - On a Shopify-connected workspace, a stale Shopify token fails per-affiliate code generation with `422 shopify_reconnect_required`; reconnect Shopify from the dashboard and retry. Response: `201 Created` with the new affiliate coupon record including `stripePromotionCodeId`. ## List + revoke List an affiliate's codes, or deactivate one. ```http GET /api/v1/affiliates/{affiliateId}/coupons DELETE /api/v1/affiliates/{affiliateId}/coupons/{id} # revokes (deactivates in Stripe + marks inactive) ``` ## Affiliate self-mint When the brand sets `selfMintMode` to `"AllAffiliates"` (or names the affiliate in `grantedAffiliateIds` under `"SpecificAffiliates"`) on a campaign coupon, that affiliate can mint their own vanity codes from /a: ```http POST /api/me/coupons/mint ``` Request body: ```json { "campaignCouponId": "c0upon01-...", "code": "SARAH-SPRING" } ``` The endpoint runs under affiliate-side auth (Clerk JWT, no org context). The caller must belong to the same program the campaign coupon targets, be on the coupon's self-mint allow-list, be `Approved` status, and stay within `maxCodesPerAffiliate` if a cap is set; the vanity code must satisfy the regex above. ## Attribution behavior On every Stripe `invoice.paid` or `customer.subscription.created` event, Rekomi extracts `discounts[].promotion_code` from the payload. If the promotion code maps to one of your `AffiliateCoupon` rows: 1. **`overridesClickAttribution=true` (default):** attribute to the coupon's owning affiliate even when a click cookie exists. `Conversion.attributionMethod = "coupon"`. 2. **`overridesClickAttribution=false`:** check metadata + customer history first. If those don't credit anyone, fall back to the coupon owner. `attributionMethod = "metadata_or_customer"` or `"coupon"` depending on which won. 3. **Checkout metadata or customer history credits the same affiliate who owns the redeemed code:** the conversion records `attributionMethod = "coupon_with_metadata"`. (The webhook never consults click cookies for this case.) The conversion row carries `affiliateCouponId` pointing at the redeemed coupon. Refund cascade uses this to decrement `redemptionCount` and `lastRedeemedAt` is also bumped on every successful conversion. When a referred visitor redeems a fully discounting (100%-off) coupon, the subscription is created but no paid invoice ever fires. When lead tracking is enabled (the default), Rekomi records that signup as a **lead** (a lead-only row, no commission), credited to the affiliate who owns the code, so it appears in your funnel even if the customer never pays. The coupon's redemption count still increments, and if the customer later starts paying, that sale is matched back to the lead and commissioned. See [Track leads and signups](/docs/developers/lead-tracking). ## Refunds When Stripe fires `charge.refunded`, Rekomi: 1. Finds the conversion by `payment_intent_id` or `charge_id`. 2. Reverses commission proportionally to the refunded amount. 3. Sets the conversion status to `Refunded` and `refundedAt` once cumulative refunds cover the full original amount; a partial refund keeps the original status. 4. If `affiliateCouponId` is set, decrements the coupon's `redemptionCount` only on the refund that transitions the conversion to fully refunded (never for partial refunds, and guarded so replays never drive the counter below zero). 5. Emits `conversion.refunded` webhook. ## Webhook events See the [Webhooks](/docs/developers/webhooks) reference. Coupon events come in two families. `campaign_coupon.created`, `campaign_coupon.updated`, `campaign_coupon.deactivated`, and `campaign_coupon.deleted` fire for the parent coupon. `affiliate_coupon.created`, `affiliate_coupon.deactivated`, and `affiliate_coupon.revoked` fire for individual per-affiliate codes. Subscribe to `*` for everything, or list each coupon event name explicitly at endpoint creation; prefix wildcards like `campaign_coupon.*` are not supported. ## Rate limits The campaign coupon and per-affiliate coupon endpoints share the standard authenticated rate-limit bucket (600 req/min per key; see [Rate limits](/docs/developers/overview#rate-limits)). Stripe-side rate limits apply independently; bulk-minting more than a few codes per second can fail with `422 stripe_create_failed`. Each request carries a stable idempotency key on the payment-provider side, so retrying the request from your side is safe and will not double-create codes. --- # Custom domain Source: https://rekomi.com/docs/developers/custom-tracking-domain > Serve the Rekomi tracking script from your own subdomain so it loads first-party and better survives tracker-blocking content filters. --- title: Custom domain description: Serve the Rekomi tracking script from your own subdomain so it loads first-party and better survives tracker-blocking content filters. --- Every brand already gets a free branded affiliate portal at `yourbrand.rekomi.com` on every plan, including Starter. Custom domain puts that same portal (and your tracking script) on a domain you own (e.g. `affiliates.brand.com`) instead of the `rekomi.com` subdomain, and it is included on every plan. Most affiliate platforms reserve first-party tracking for their most expensive plans; on Rekomi it comes with every plan. It swaps `api.rekomi.com` for the domain you control in the tracking script URL. The browser then treats every tracking request as first-party from your site's perspective, which: - Sidesteps ad blockers that match generic patterns on third-party tracking hosts. - Avoids Safari ITP's third-party cookie partitioning. - Avoids Chrome's storage partitioning for embedded scripts. A custom domain improves click capture and first-party cookie survival, which protects attribution for the sales your processor or server integration reports. Conversions themselves are recorded server-side (through your payment processor connection or the server-to-server API) and are never blocked by content filters, so the domain helps the click-tracking path rather than the conversion path. This is one unified feature: portal white-label plus first-party tracking on a single host you own. And if your org has [white-label](/docs/brands/white-label) on (Pro), the same domain also serves your affiliates' **sign-in** at `/login` - your custom domain becomes the one place your affiliates ever go. ## Prerequisites - Any Rekomi plan (Starter and up all include it). - DNS administrative access for the domain you want to use (e.g. `brand.com`). - About 5 minutes for the setup, plus 30-60 seconds for our system to provision a TLS certificate on the brand's subdomain. ## Setup ### 1. Pick your subdomain In **Dashboard > Settings > Custom domain**, enter a subdomain you control. Most brands use `affiliates.` or `go.`. You can use any subdomain you own. This one subdomain does both jobs: it loads your tracking script first-party **and** hosts your affiliate signup pages (your primary campaign at the root, each campaign at `/`). There are no separate per-campaign domains to set up. One subdomain covers your whole program. Common choices: - `affiliates.brand.com` - `go.brand.com` - `partners.brand.com` - `track.brand.com` Pick one that doesn't already point at another service. We'll show you the exact CNAME record to add in the next step. ### 2. Add the CNAME record After saving the subdomain, the dashboard displays one CNAME record: ``` affiliates.brand.com CNAME cname.rekomi.com ``` The card also detects where the parent domain's DNS is managed and shows it ("Your DNS is managed at Cloudflare"), with a link into that provider's console. **If your DNS is at Cloudflare, you do not have to touch DNS at all.** The card offers an **Add it for me** button: it opens Cloudflare's consent page, which shows exactly this one CNAME under Rekomi's name and logo. Approve it and Cloudflare creates the record for you, as **DNS only** and at the right subdomain, then sends you back to the card, which verifies the record automatically; there is nothing else to click. When one-click is available the card leads with it and tucks the copy-paste table behind an "Add it manually instead" link. The request is cryptographically signed, so Cloudflare checks it really came from Rekomi before showing the consent screen. One-click covers Cloudflare-managed DNS today; more providers are planned. ![The custom domain card offering the one-click Add it for me option for a Cloudflare-managed domain](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/brands/settings-custom-domain.png?v=5) Everyone else adds the record by hand at their DNS provider (Namecheap, Route53, GoDaddy, Squarespace, or wherever you manage DNS for the parent domain). Save the record and wait for DNS propagation (usually under a minute, sometimes up to a few minutes). If you add the record by hand at Cloudflare, set its proxy status to **DNS only** (gray cloud), not Proxied; the **Add it for me** flow already does this for you. Your subdomain needs to CNAME straight through to our edge (`cname.rekomi.com`); proxying it on your side puts another TLS layer in the way and breaks certificate issuance. ### 3. Verify Click **Verify** in the dashboard. (If you used **Add it for me**, skip this: verification runs on its own as soon as you land back on the card.) We do a DNS lookup on your subdomain and check that the CNAME chain resolves to `cname.rekomi.com` (or an equivalent Rekomi-controlled target). If verification passes, we automatically: - Register your subdomain on our Cloudflare-for-SaaS edge. - Issue a TLS certificate for it automatically. - Update your install snippet to use your subdomain instead of `api.rekomi.com`. - Mark your domain **verified**: adding the CNAME requires control of your DNS, which proves domain ownership, so your website appears as a verified pill on your signup and About pages (next to your [verified socials](/docs/brands/settings#verified-socials)). The dashboard shows a "Provisioning certificate" indicator while the cert is being issued (~30-60s typical). During this window your install snippet stays pointed at `api.rekomi.com` so your site keeps working uninterrupted. Once the cert is active, the snippet auto-switches. ### 4. Re-copy your install snippet If you pasted the install snippet on your site before verifying your custom domain, copy it again now. The new snippet uses your subdomain. If you call `Rekomi.convert()` to record signups, that code does not need to change; the loader picks up your domain automatically. ## Affiliate signup on your custom domain The same subdomain also serves your affiliate signup pages, so your whole program lives on one domain. There are no separate per-campaign domains to configure: - `https://affiliates.brand.com/` is your primary campaign's signup page. - `https://affiliates.brand.com/` is each campaign's own signup page. Share these links with affiliates instead of the free `yourbrand.rekomi.com` versions. The page loads from your own domain, so it reinforces your brand and avoids the third-party look. Tracking and the signup pages share the one subdomain; nothing extra to set up per campaign. ## How it works under the hood 1. Your visitor loads `brand.com`. 2. Your install snippet pulls `affiliates.brand.com/api/v1/r/loader.js` (or `api.rekomi.com` if you skipped the custom domain). 3. Browser resolves `affiliates.brand.com` via your DNS: `affiliates.brand.com` → CNAME → `cname.rekomi.com` → ultimately our hosting provider. 4. Request hits our routing layer. Our routing layer sees the `Host: affiliates.brand.com` header, matches it to your org (registered when you verified), and serves the loader script. 5. The loader script sends click events to `affiliates.brand.com/api/v1/r/c` first-party. From the browser's perspective these are first-party calls; cookies set by these responses are first-party to `brand.com`. Conversions are not sent by the loader; they are recorded through your payment processor connection or the server-to-server API and are unaffected by tracking-host blocking. Because the script is loaded from your own subdomain, generic tracker filters (EasyPrivacy, uBlock standard lists) don't match the URL pattern, and the cookies set by click responses survive Safari ITP's third-party caps. ## Failure states + recovery The dashboard shows one of these statuses: | Status | What it means | What to do | |---|---|---| | **Pending** | You saved the subdomain but haven't added the CNAME yet (or DNS hasn't propagated) | Add the CNAME at your DNS provider. Click Verify. | | **Failed** | DNS verification couldn't find the expected CNAME target | Check that the CNAME record exists and points at `cname.rekomi.com`. If you added it by hand at Cloudflare, set proxy to DNS only (the Add it for me flow does this automatically). Click Verify. | | **Verified + Provisioning** | DNS is verified; cert is being issued | Wait 30-60 seconds. The dashboard auto-polls. | | **Verified + Active** | Routing is live | Re-copy your install snippet if you pasted it before this point. | | **Verified + Error** | Cert provisioning failed | Check for conflicting A/AAAA records on the same subdomain. Click Verify to retry. | ## Disconnecting To stop using a custom domain, open **Settings > Custom domain** and click **Disconnect**. You'll be asked to type a confirm phrase (defense against accidental clicks). After disconnect: - We remove the subdomain from our routing layer. - Your install snippet reverts to `api.rekomi.com`. - You should remove the CNAME from your DNS provider for hygiene. - Re-copy the install snippet to your site. In-flight tracking (clicks already attributed) is not affected. Future tracking falls back to the default host. ## What this does NOT solve - **Cross-domain attribution** (visitor clicks on phone, buys on desktop): custom domains don't help. Cross-device tracking requires login-bound attribution. - **Domain-level blocklists** (`api.rekomi.com` explicitly added to a user's blocklist): if a user adds your subdomain to their blocklist too, this helps the average case but not the targeted case. - **Brand-paid Cloudflare for SaaS / Argo / strict Bot Fight Mode**: complex CF setups in front of your domain can interfere with our cert provisioning. Use DNS-only mode on the tracking subdomain. - **Processor-tracked conversions**: those flow through your payment processor's own webhook and aren't affected by tracking-host blocking. Custom domain only helps the click-tracking path. ## Pricing Included on every plan, Starter and up. ## Related - [S2S tracking](/docs/developers/s2s-tracking) - [Tracking script](/docs/developers/tracking-script) --- # White-label embed Source: https://rekomi.com/docs/developers/embed > Fetch an affiliate's earnings data with a per-affiliate token and render it inside your own product. --- title: White-label embed description: Fetch an affiliate's earnings data with a per-affiliate token and render it inside your own product. --- The embed feature lets a brand surface each affiliate's earnings inside their own product. You mint a per-affiliate token, fetch the affiliate's earnings data with it from your own page, and render the numbers in your UI. Gated to Growth and higher. ## What it does An affiliate logs into your product as usual. Your page fetches Rekomi's embed data endpoint with a per-affiliate token and renders the affiliate's earnings and recent conversions in your own interface. The affiliate never has to leave your domain or log in to Rekomi separately. Read-only. The endpoint returns data; it does not let the affiliate take actions (no apply, no profile edit, no tax form upload). For those actions they still sign in to their Rekomi affiliate dashboard. ## Generate a token Token minting requires an account Owner or Admin (or an API key with read and write access) on a Growth or higher plan. Manager and Viewer members cannot mint or revoke tokens. ```http POST /api/v1/embed/tokens Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxx Content-Type: application/json { "affiliateId": "b3d7e2a1-4c9f-4e8a-9f21-0a1b2c3d4e5f", "allowedOrigins": "https://yourapp.com,https://app.yourapp.com", "rotate": false, "expiresInHours": 24 } ``` Required: - `affiliateId`: the affiliate (a UUID) to scope this token to. Must be an affiliate in one of your programs. - `allowedOrigins`: comma-separated list of origins (scheme + host + port) that can frame the embed. Strict equality match; no wildcards. Effectively required: the mint succeeds without it, but every data fetch returns 403 `no_allowed_origins_configured` until you rotate the token with origins set. Optional: - `rotate` (boolean, default `false`): when `true`, replaces any existing token for the (program, affiliate) pair with a fresh one. When `false` and a token already exists, the response returns the existing token's metadata (id, allowed origins, expires) with `alreadyExists: true` and no plaintext token; you must `rotate: true` to mint a new plaintext. - `expiresInHours` (integer, default `24`, clamped to `[1, 720]`): time-to-live in hours. Beyond 30 days (720 hours), rotate rather than extend. Response (new or rotated token): ```json { "token": "kY3xTq8Zr1Nf6Wb2Jm9Lp4Vc7Hd0Ag", "allowedOrigins": "https://yourapp.com,https://app.yourapp.com", "expiresAt": "2026-05-12T03:14:25.000Z" } ``` The token is an opaque 24-byte URL-safe random string with no prefix. The plaintext `token` is returned EXACTLY ONCE. Store it in your application server before passing to the browser; we cannot recover it. Hash-at-rest on our side; lookup on the public dashboard endpoint hashes the inbound token before compare. Tokens default to 24 hours. Generate fresh tokens when the affiliate signs in. Do not pass long-lived tokens to the browser. ## Fetch the data and render it The dashboard endpoint returns JSON, not an HTML page. Fetch it from your own page (a cross-origin fetch sends the `Origin` header the allowlist checks) and render the earnings UI in your product, theming with the returned brand color. ```js const res = await fetch( "https://api.rekomi.com/api/embed/public/dashboard?token=kY3xTq8Zr1Nf6Wb2Jm9Lp4Vc7Hd0Ag" ); const data = await res.json(); // render data.earnedCents, data.recentConversions, etc. in your own UI ``` The response carries read-only earnings and recent conversions for the affiliate the token was issued to, plus your brand color and name so you can theme the panel (see Customization below). The `Origin` header your browser sends on the cross-origin fetch must exactly match one of the token's `allowedOrigins`, so call it from a page served on an allowlisted origin. ## Origin allowlist The embed enforces a strict origin check. The `Origin` header sent by the browser (on the cross-origin fetch) must exactly equal one of the entries in `allowedOrigins`. Comparison is: - Scheme (https vs http) - Host - Port (explicit if non-default) No `StartsWith` checks. No subdomain wildcards. Required because a `StartsWith` rule would allow `https://yourapp.com.evil.example.com` to bypass. If you need multiple origins, add them all to the list. If you serve from many subdomains, generate per-deploy tokens that include only the relevant origin. ## What the embed shows The `GET /api/embed/public/dashboard?token=...` endpoint returns: | Field | Type | Description | |---|---|---| | `affiliateName` | string\|null | Affiliate's full name (falls back to email if no name set) | | `programName` | string\|null | The program the affiliate is in (within your org) | | `earnedCents` | integer | Commission earned (sum of `commissionCents` across Approved + Paid conversions) across the affiliate's recent conversions | | `paidCents` | integer | Subset of `earnedCents` that has been paid out | | `pendingCents` | integer | `earnedCents - paidCents` | | `brandColor` | string | Your org's brand color (falls back to `#0E7C7B` if unset). Use to theme the panel. | | `brandName` | string\|null | Your org's display name. Surface in your panel header. | | `recentConversions[]` | array | Up to 20 most recent conversions, newest first. Each entry: `{ id, amountCents, commissionCents, status, createdAt }`. | The `earnedCents`, `paidCents`, and `pendingCents` totals are computed over the affiliate's 50 most recent conversions, not lifetime history, so they can understate totals for high-volume affiliates. Realistic JSON response: ```json { "affiliateName": "Jane Doe", "programName": "Default program", "earnedCents": 12450, "paidCents": 9800, "pendingCents": 2650, "brandColor": "#0E7C7B", "brandName": "Your Brand", "recentConversions": [ { "id": "c0nv0001-...", "amountCents": 9900, "commissionCents": 1980, "status": "Approved", "createdAt": "2026-05-10T16:00:00Z" } ] } ``` That is the full payload. No tax forms, no Stripe Connect status, no settings, no IPs, no PII beyond what the affiliate has consented to display. ## What the embed does NOT show - Other organizations' programs (even if the affiliate is in multiple) - Tax form status (PII) - Stripe Connect bank details (PII) - Other affiliates' data The embed is strictly scoped to one (organization, affiliate) pair. ## Customization The dashboard response surfaces `brandColor` (from your organization's brand color setting at `/dashboard/settings/branding`, defaulting to `#0E7C7B` when unset) and `brandName` (your org's display name). Render your panel on your side using those values to match your product's look. Text color, layout, and typography are not server-customizable; the endpoint returns data only. Future releases will add a customization JSON (hide/show columns, header text, custom CSS variables) on the token creation request. ## Plan gate `POST /api/v1/embed/tokens` is gated to Growth plan and higher. If your org is on Starter, the endpoint returns HTTP 402 with `{ error: "plan_tier_required", required: "Growth", current: "Starter" }`. ## Security checklist - Always generate tokens server-side. Never expose the bearer API key to the browser. - Always set `allowedOrigins` to the exact production origin. - Generate fresh tokens per session (do not reuse a 24-hour token across many sessions). - Verify the `Authorization` of your incoming user request before minting a token (only the actual logged-in affiliate should get a token for their own data). - Fetch the data endpoint only from pages served on an allowlisted origin, so the browser sends the `Origin` header the allowlist checks. ## Troubleshooting - **`origin_not_allowed`**: your `Origin` header does not match anything in `allowedOrigins`. Check scheme, host, port for exact equality. A malformed `Origin` value returns `origin_invalid` instead. - **`origin_required`**: the request did not send an `Origin` header (e.g., curl, server-to-server). The dashboard endpoint requires `Origin` even when `allowedOrigins` is set, so fetch it cross-origin from a browser page rather than server-side. - **`token_expired` (401)**: the token's `expiresAt` is in the past. Generate a fresh one on next session. - **Token mint returns 404**: the `affiliateId` is not an affiliate in your organization. It must reference an affiliate you own. - **Empty response or CORS error**: check your browser console. Confirm you are fetching from a page whose origin is on the token's `allowedOrigins`, and that the token has not been revoked (an unknown or revoked token returns 401). --- # Track leads and signups Source: https://rekomi.com/docs/developers/lead-tracking > 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. --- title: Track leads and signups description: 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. --- A **lead** is a free identified signup: an email tied to a referral, recorded **before** any payment. Recording a lead buys you two things: 1. **Durable email-match attribution.** When the same email later pays, the sale is credited to the referring affiliate even if the click cookie was cleared, blocked, or expired in the meantime. 2. **A Visitor → Lead → Sale funnel.** Free signups become a real acquisition stage in your dashboard and insights, not just clicks and sales. A lead does **not** mint a commission on its own. It records `email + name` against the referral; the commission still happens when the paid sale comes through your processor or the [S2S API](/docs/developers/s2s-tracking). ## Enabled by default Lead tracking is **enabled for every brand by default**. The automatic capture below and the browser / S2S endpoints work as soon as the [tracking loader](/docs/developers/tracking-script) is installed. Recording a lead (identifying a referred signup by email) is live for every brand today. Paying affiliates for leads is a separate leg of the campaign: tick Leads under **What do you pay affiliates for?** on the campaign form and set the per-lead rate (see [Pay per click or lead](/docs/brands/cpc-and-cpl)). This page covers lead **identification** so a later sale still credits the affiliate; it does not mint a commission on the signup itself. ## Automatic for Stripe, Paddle, Chargebee, Recurly, and Creem For **Stripe, Paddle, Chargebee, Recurly, and Creem**, leads are captured **automatically, with zero brand code**. You need nothing beyond what you already have: - the normal [tracking loader](/docs/developers/tracking-script) (`loader.js`) on your site, and - your existing processor connection. When a referred visitor signs up for a **subscription or a free trial**, Rekomi records a lead straight from the processor webhook your brand already receives. No `convert()` call, no signup form wiring. The **first real payment** is then recorded as the **Sale**, and it is attributed back to the same affiliate (the paid charge matches the lead by customer history). This includes **free / fully-discounted coupon signups**. When a referred visitor redeems a 100%-off (or otherwise fully discounting) coupon, the subscription is created but no paid invoice ever fires. Rekomi still records that signup as a **lead**, so it shows up in your funnel even if the customer never pays. The coupon's redemption count still increments and the lead is attributed to the affiliate who owns the code. ### Which processors capture leads automatically? | Processor | Automatic leads? | How to capture a lead | |---|---|---| | **Stripe** | **Yes** | Automatic on subscription / trial signup. No code. | | **Paddle** | **Yes** | Automatic on subscription / trial signup. No code. | | **Chargebee** | **Yes** | Automatic on subscription / trial signup. No code. | | **Recurly** | **Yes** | Automatic on subscription / trial signup. No code. | | **Creem** | **Yes** | Automatic on subscription / trial signup. No code. | | **Braintree** | No | Use `Rekomi.convert()`. | | **Shopify** | No | Use `Rekomi.convert()`. | | **Lemon Squeezy** | No | Use `Rekomi.convert()`. | | **Polar** | Trials only | Automatic on trial start (the trial's $0 order records a $0 lead). Use `Rekomi.convert()` for non-trial signups. | | **Gumroad** | No | Use `Rekomi.convert()`. | | **Dodo Payments** | No | Use `Rekomi.convert()`. | | **Mollie** | No | Use `Rekomi.convert()`. The sale is matched to the lead by the buyer email: `billingAddress.email` on the payment, or the email on the Mollie customer record when the payment carries none (a token created before `customers.read` was added to the Mollie permission list cannot read it, so reconnect with a fresh seven-permission token). | Leads are captured automatically, with zero code, for **Stripe, Paddle, Chargebee, Recurly, and Creem**: when a referred visitor starts a subscription or trial, the processor webhook records the lead. For **Braintree, Shopify, Lemon Squeezy, Gumroad, Dodo Payments, and Mollie** there is no automatic lead capture; Rekomi does not receive a usable pre-payment signup event for them, so capture those leads with `Rekomi.convert()` in the browser instead. On Mollie, also pass the same email as `billingAddress.email` on one-time payments (or stamp `metadata.rekomi_ref` on them): the hosted checkout runs no script, so the sale arrives through the webhook and the email is what matches it to the lead; a payment that belongs to a Mollie customer is matched by that customer record's email instead. **Polar** sits in between: an attributed trial-start subscription automatically records a $0 lead (the trial's $0 order carries the referral), but non-trial checkouts have no pre-payment signup event, so capture those with `Rekomi.convert()`. The [S2S lead endpoint](#server-to-server-method) is reserved for organizations without a native processor connection: while Stripe (via the Rekomi app for Stripe), Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, Mollie, or the Shopify app is connected, it returns a `409` (`*_connect_active`) to keep one conversion source per workspace. ## Browser method: `Rekomi.convert()` The browser `Rekomi.convert()` call records a lead at account creation. With Stripe, Paddle, Chargebee, Recurly, and Creem you do not need it for subscription / trial signups; those are already automatic. It is the **primary** lead path in the cases below. ### Freemium and free plans If your product has a **free tier** where signing up does **not** create a Stripe or Paddle subscription or customer, there is no processor event for Rekomi to turn into a lead automatically. This is roughly half of SaaS, so for those products `Rekomi.convert()` is the main way leads are captured, not a fallback. Call `Rekomi.convert({ email })` at **account creation**, the moment the free account is made: ```js Rekomi.convert({ email: "user@example.com" }); ``` **Pass the same email the customer will use for billing.** When that free user later upgrades to a paid plan, Rekomi matches the sale back to this lead **by email** (the email-match attribution below), so the referring affiliate is still credited even though the click cookie is long gone. This is what carries attribution across the free-to-paid gap, which can be weeks or months: > Visitor (click) -> free signup (lead via `convert`) -> later upgrade (sale, email-matched to the lead) ### Other cases for `convert()` `Rekomi.convert()` is also the right tool for: - **Braintree, Shopify, Lemon Squeezy, Gumroad, Dodo Payments, and Mollie**, where Rekomi does not capture a lead automatically, so `convert()` is how you record the signup (on Mollie, pass the same email as `billingAddress.email` on one-time payments so the sale matches the lead). On **Polar**, trial starts already record a $0 lead automatically; `convert()` covers non-trial signups. - **Capturing the lead earlier or richer**: at the signup form, before checkout, or together with a `name`. If you cannot run your own code on the page (a hosted platform such as beehiiv), use the loader attribute described in [Three ways to record a lead in the browser](#three-ways-to-record-a-lead-in-the-browser) instead. On beehiiv, Kit, MailerLite, Ontraport and GoHighLevel you may not need a lead at all: the platform connection credits sales from the platform's own signup record and pushes tagged signups to Rekomi as leads; see [Leads and sales from your platform](#leads-and-sales-from-your-platform). This is also the direct replacement for Rewardful's `rewardful('convert', { email })`, which makes it the easy path for teams migrating from Rewardful. After the [tracking script](/docs/developers/tracking-script) (`loader.js`) is installed, call `Rekomi.convert(...)` at signup, the moment a user creates an account: ```js Rekomi.convert({ email: "user@example.com", name: "Jane Doe" }); ``` `name` is optional. There is also an `identify` alias and a string shorthand, all equivalent: ```js Rekomi.identify({ email: "user@example.com", name: "Jane Doe" }); Rekomi.convert("user@example.com"); ``` The call posts to `/api/v1/r/lead/signup` and is a **no-op** when there is no referral context or no email, so it is safe to call unconditionally. To be explicit and only fire for referred visitors, wrap it in a `ready()` callback: ```js Rekomi.ready(function (R) { if (!R.referral) return; // visitor was not referred, nothing to record Rekomi.convert({ email: signupForm.email, name: signupForm.name, }); }); ``` `ready()` waits for the referral context to resolve, so you can read `R.referral` reliably before deciding whether to record the lead. See the full [`window.Rekomi` API](/docs/developers/tracking-script) for `referral`, `affiliate`, `campaign`, and `ready()`. `Rekomi.convert({ email })` is the direct replacement for `rewardful('convert', { email })`. The semantics match: a free signup is recorded as a lead, and the paid charge is the conversion. On Stripe, Paddle, Chargebee, Recurly, and Creem you can often drop the call entirely, since the lead is captured automatically. ## Three ways to record a lead in the browser All three run on top of the [tracking loader](/docs/developers/tracking-script). Pick the one that matches where the email is typed. 1. **`window.Rekomi.convert({ email })` from your own code.** Use it when you control the signup flow: call it the moment the account is created, as shown above. It is the most precise option and the only one that can pass a `name`. 2. **`data-email-selector` on the loader tag, for a classic HTML form submit.** Point it at your email input, or add `data-rekomi-email` to the input itself. The loader reads that value when the form is submitted, so you write no code. 3. **`data-rkmi-email-capture="auto"` on the loader tag, on by default in the tag Rekomi hands out.** The loader records the lead the moment a referred visitor finishes typing an email into any email or text field (on change or focus-out), and it also sweeps the inputs of any submitted form, so it covers hosted platforms and in-page checkouts where the page never submits a form: a platform login step that is a plain text field, or a checkout that runs inside a pricing block. This is what lets a sale be credited even when your checkout passes no referral. Remove the attribute to turn it off; explicit `Rekomi.convert()` calls still work, and the loader falls back to the submit-based capture in option 2. On beehiiv and Kit this is optional once the platform connection has **Credit sales** on: it then only covers readers who were already subscribed before they clicked. ```html ``` If you added a separate lead-capture snippet from an earlier version of a Rekomi install guide, delete it; the attribute replaces it. The same guarantees apply to all three. Nothing is recorded without a referral, each email is sent once per browser session, and the server dedups. Plus-addressed variants of one inbox (you+test@gmail.com and you@gmail.com) count as the same person for matching. On a hosted platform without a Credit sales connection, a purchase that reaches Rekomi with no matching lead does not appear as a sale. If that happens, check two things: the loader tag carries the attribute, and the email was typed on a page where the loader runs. On beehiiv, Kit, MailerLite, Ontraport and GoHighLevel the platform lookup runs after the lead match, so the sale can still be credited without a lead. ## Server-to-server method When signups happen on your backend, record the lead server-side instead: ``` POST /api/tracking/lead ``` It uses the **same authentication as the S2S sale postback**: a `Authorization: Bearer rk_live_*` API key plus an `X-Rekomi-Signature` HMAC-SHA256 header. The signing computation is identical to the sale endpoint, so reuse the same helper. See [Server-to-server tracking](/docs/developers/s2s-tracking#signature-computation) for the signing details and code in Node.js, Python, and other languages. The endpoint requires a Starter or higher plan (14-day trials accepted; lower tiers get 402 `plan_tier_required`) and an approved affiliate (a non-approved affiliate returns 400 `affiliate_not_active`). An optional `programId` body field disambiguates account-level slugs that span multiple campaigns. Body: ```json { "affiliateSlug": "jane-recommends", "email": "user@example.com", "name": "Jane Doe", "externalEventId": "signup_abc123" } ``` - `affiliateSlug` (required): the affiliate to credit. From the affiliate's tracking URL. Matched case-insensitively, so `SERKAN` and `serkan` credit the same affiliate. - `email` (required): the customer's email. This is what a later sale is matched against. - `name` (optional): the customer's display name. - `externalEventId` (optional): your unique id for this signup. Used for de-duplication, so retries are safe. Omit it unless you need your own retry key: without it, duplicate signups for the same affiliate and email collapse into one lead across the browser, server, and webhook paths. Supplying your own id keys the lead on that id instead, so the same person recorded through both the browser and a backend call with a custom id can become two lead rows. Use the S2S path whenever the signup originates on your server, you can keep a secret server-side, and your workspace has **no native processor connection**. While Stripe (via the Rekomi app for Stripe), Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, Mollie, or the Shopify app is connected, this endpoint returns `409` with the matching `*_connect_active` error (one conversion source per workspace); capture leads with `Rekomi.convert()` there instead. It is the right path for gateways you drive entirely over [S2S](/docs/developers/s2s-tracking). ## MCP method For AI-assistant and automation flows, the [MCP server](/docs/developers/mcp) exposes a `record_lead` tool. Inputs: `affiliateSlug`, `email`, optional `name`, and optional `campaignId`. It records a signup lead the same way the browser and S2S paths do. ## How attribution works Email-match is a **fallback only**. It never overrides an attribution Rekomi already has. The resolver tries, in order: 1. Coupon-code override 2. Processor metadata (e.g. the Stripe `rekomi_affiliate_id`) or any other referral passed on the payment 3. Prior conversion by customer history 4. Coupon 5. An email match against a recorded lead (this page) 6. The platform lookup, on beehiiv, Kit, MailerLite, Ontraport and GoHighLevel connections with **Credit sales** on (the [section below](#leads-and-sales-from-your-platform)) Only when **all** of steps 1 to 4 miss does Rekomi fall back to matching the sale's email against a recorded lead, and only when that misses too does it ask the platform. Step 5 runs the same way on every gateway Rekomi receives sales from over its API (Stripe, Paddle, Braintree, Chargebee, Polar, Recurly, Creem, Dodo Payments, Lemon Squeezy, Gumroad and Mollie) and on the beehiiv, Kit, MailerLite, Ontraport and GoHighLevel connections. The lead match is: - **Second to a passed referral.** A referral the payment already carries always wins. The email match only fills in when the sale arrived with none. - **Scoped to your own workspace.** A lead in one workspace can never credit a sale in another. - **Aged to 90 days.** A sale is matched only against a lead recorded within the last 90 days; the newest qualifying lead wins. - **Not for an affiliate's own purchase.** When the buyer email is the email of the affiliate the lead points to, the match is skipped and the sale is not credited. Two paths keep their earlier behaviour and credit that sale: Stripe, and sales recorded through the platform connections (Kit Commerce purchases, Ontraport transactions and GoHighLevel orders). - **Read from the Mollie customer when the payment has no email.** A Mollie payment created without `billingAddress.email` is matched by the email on its Mollie customer record instead, when the payment belongs to a Mollie customer. Reading it uses the `customers.read` permission, one of the seven the Mollie connection asks for; a token created before `customers.read` was added does not carry it, so a brand that connected earlier reconnects with a fresh seven-permission token and passes `billingAddress.email` on the payments it creates itself until then. Because it runs after every processor-native step, existing attribution is unchanged and sales are never mis-credited. For the automatic processor path (Stripe, Paddle, Chargebee, Recurly, Creem), the later paid charge already attributes by customer history (step 3 finds the lead row by customer id), so email-match is not even needed for the same-customer case. Brands not using leads see no difference at all. ## Leads and sales from your platform On beehiiv, Kit, MailerLite, Ontraport and GoHighLevel, the CRM connection under **Integrations** does two jobs, each with its own switch: **Sync affiliates into** the platform, and **Credit sales through** the platform. With the second switch on (it is on by default): - Every affiliate link Rekomi hands out carries `utm_source=rekomi` and `utm_campaign` set to the affiliate's link slug, for example `https://newsletter.example.com?via=jane&utm_source=rekomi&utm_campaign=jane`. The platform stores those values on the person at signup: beehiiv, Kit, Ontraport and GoHighLevel natively, MailerLite in one custom field named **Rekomi affiliate tag** that the Rekomi loader fills from the link. - When a sale reaches Rekomi with no affiliate named by steps 1 to 5 above, Rekomi looks the buyer up on the platform by email, reads the stored tag, and credits the affiliate. It is the last step of the resolver, it runs inside a three-second budget, and it can never delay or fail the sale itself. If the platform is slow or has not created the person yet, Rekomi retries a few times over the next two hours and credits the sale once at most. - Where the platform can send them, tagged signups reach Rekomi as leads too: beehiiv (Scale plan and above), Kit, MailerLite and Ontraport through webhooks Rekomi registers when you connect, GoHighLevel through a workflow webhook you add. Those leads collapse into the same row as a lead recorded by the loader or the S2S endpoint, so nothing is counted twice. - A lead you recorded yourself still wins: step 5 runs before the platform lookup. Where the sale itself comes from differs by platform. beehiiv and MailerLite sales arrive through the Stripe account you connect under Setup (for beehiiv, the account beehiiv created for paid subscriptions). Kit Commerce sales arrive through a purchase feed Rekomi registers with Kit. Ontraport sales arrive through the Ontraport connection itself. GoHighLevel recurring products arrive through your connected Stripe, and one-time order forms or non-Stripe gateways through a workflow webhook. Flodesk has nowhere to store the tag, so its connection has no Credit sales switch; use the loader and a connected gateway there. The prerequisites, the status line, the **Check a subscriber's email** box and the limits (the main one: beehiiv and Kit write the arrival once, so a subscriber who joined before clicking is not credited by the lookup) are on each platform's page: [beehiiv](/docs/brands/crm/beehiiv), [Kit](/docs/brands/crm/convertkit), [MailerLite](/docs/brands/crm/mailerlite), [Ontraport](/docs/brands/crm/ontraport), [GoHighLevel](/docs/brands/crm/gohighlevel). ## Where leads show up Once leads are flowing, the dashboard surfaces a **Visitor → Lead → Sale** funnel and lead counts alongside clicks and conversions, in the campaign's Performance tab, Leads section, and in Reports. The funnel stage appears adaptively: brands that never record a lead do not see an empty Lead row. --- # MCP server Source: https://rekomi.com/docs/developers/mcp > Connect AI assistants (Claude, Cursor, Continue, Zed, ChatGPT, Windsurf) to your Rekomi account through Model Context Protocol. --- title: MCP server description: Connect AI assistants (Claude, Cursor, Continue, Zed, ChatGPT, Windsurf) to your Rekomi account through Model Context Protocol. --- Rekomi runs a hosted MCP server at `https://mcp.rekomi.com/api/mcp`. Pick your AI client below, paste two values, restart. That's it. } title="Claude Desktop" href="#connect-claude-desktop" description="Paste two lines into claude_desktop_config.json. Restart." /> } title="Claude Code" href="#connect-claude-code" description="One claude mcp add command. CLI persists the config." /> } title="ChatGPT" href="#connect-chatgpt" description="Settings → Connectors → Add custom connector. UI only." /> } title="Cursor" href="#connect-cursor" description="Add to ~/.cursor/mcp.json. Same JSON shape as Claude." /> } title="Windsurf" href="#connect-windsurf" description="Cascade panel → MCP servers → Add. Streamable-HTTP." /> } title="Continue" href="#connect-continue" description="Add to ~/.continue/config.json. Restart the extension." /> } title="Zed" href="#connect-zed" description="Add to ~/.config/zed/settings.json under context_servers." /> ## Generate a connection token Every connection needs an `rk_mcp_*` token. It's the only credential your AI client uses. 1. Sign in to [app.rekomi.com](https://app.rekomi.com). 2. Open **Settings → AI assistant**. 3. Click **Generate connection token**, give it a name (e.g. *Claude Desktop laptop*), and copy the `rk_mcp_*` value. It is shown exactly once. Owner-only to create. Up to 10 active per organization. Revoke at any time; the change propagates within ~10 seconds. Available on every plan, including during the trial.

Connect Claude Desktop

Edit your `claude_desktop_config.json`: ```json title="claude_desktop_config.json" { "mcpServers": { "rekomi": { "url": "https://mcp.rekomi.com/api/mcp", "headers": { "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE" } } } } ``` Restart Claude Desktop. Ask it "what's in my Rekomi account?" to confirm.

Connect Claude Code

```bash claude mcp add --transport http rekomi https://mcp.rekomi.com/api/mcp \ --header "Authorization: Bearer rk_mcp_YOUR_TOKEN_HERE" ``` The URL **must** come before `--header`. The `--header` flag is variadic, so any positional argument after it gets eaten as another header. If you see `error: missing required argument 'commandOrUrl'`, that's the cause. The CLI persists the config to `~/.claude.json` and subsequent sessions auto-connect.

Connect ChatGPT

ChatGPT supports MCP via the Connectors UI on chatgpt.com (Plus, Pro, Team, Enterprise, 2026 Apps SDK rollout). There's no JSON config to paste; the setup is in the UI. 1. Open chatgpt.com → profile menu (bottom-left) → **Settings** → **Connectors** → **Add custom connector**. 2. Name: `Rekomi`. 3. MCP server URL: `https://mcp.rekomi.com/api/mcp` 4. Authentication: **Custom header**. - Header name: `Authorization` - Header value: `Bearer rk_mcp_YOUR_TOKEN_HERE` 5. Save. Start a new chat, enable the Rekomi connector, ask "list my pending Rekomi affiliates".

Connect Cursor

Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project root: ```json title="~/.cursor/mcp.json" { "mcpServers": { "rekomi": { "url": "https://mcp.rekomi.com/api/mcp", "headers": { "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE" } } } } ```

Connect Windsurf

Open the Cascade panel and click the hammer/plug icon → **MCP servers** → **Add server**. Pick Streamable-HTTP transport and paste: ```json title="~/.codeium/windsurf/mcp_config.json" { "mcpServers": { "rekomi": { "serverUrl": "https://mcp.rekomi.com/api/mcp", "headers": { "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE" } } } } ``` Restart Windsurf. The Rekomi tools appear under **Available tools** in the Cascade panel.

Connect Continue

Add to `~/.continue/config.json` under `experimental.modelContextProtocolServers`, then restart the Continue extension: ```json title="~/.continue/config.json" { "experimental": { "modelContextProtocolServers": [ { "transport": { "type": "streamable-http", "url": "https://mcp.rekomi.com/api/mcp" }, "headers": { "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE" } } ] } } ```

Connect Zed

Add to `~/.config/zed/settings.json` under `context_servers`: ```json title="~/.config/zed/settings.json" { "context_servers": { "rekomi": { "source": "custom", "command": null, "settings": { "url": "https://mcp.rekomi.com/api/mcp", "headers": { "Authorization": "Bearer rk_mcp_YOUR_TOKEN_HERE" } } } } } ``` ## How it works AI assistants speak MCP to read your affiliate program, approve applications, draft outreach, mint embed tokens, and more, all on behalf of the human asking. Every tool call lands in your audit log with `actor_type = mcp`. MCP works on every plan, including during the trial; feature-tier gates (e.g. embed tokens are Growth+) still apply regardless of how the call arrives. > Machine-readable docs for LLM-driven IDEs and agents: this page as raw Markdown at [https://rekomi.com/docs/developers/mcp.md](https://rekomi.com/docs/developers/mcp.md), regenerated on every release. ``` AI client ──Bearer rk_mcp_xxx──► mcp.rekomi.com/api/mcp │ │ Forwards + co-signs: │ Authorization: Bearer rk_mcp_xxx │ X-Rekomi-MCP-Identity: t=,v1= ▼ api.rekomi.com/api/v1/* │ │ Validates the HMAC, validates the token, │ checks role + scope, runs the action, │ logs to audit log with actor_type=mcp. ▼ Your data ``` The token alone is useless. The backend rejects any direct call to `api.rekomi.com` with an `rk_mcp_*` token with a generic 401 `{ "error": "unauthorized", "message": "Missing or invalid credentials." }`. The trusted relay at `mcp.rekomi.com` is the only path that produces the HMAC the backend requires. Transport is MCP Streamable-HTTP in stateless mode: each tool call is a self-contained JSON-RPC request/response over HTTPS. No long-lived sessions, no SSE upgrade. A `GET https://mcp.rekomi.com/api/mcp` returns a small JSON identity blob you can use as a health check. ## Available tools There are 61 tools (35 reads and 26 safe writes). Each tool maps 1:1 to one Rekomi API endpoint (or, for the install-recipe tools, to a local content registry). Composition (e.g. "approve every pending affiliate") happens in your AI client's reasoning loop, not inside a tool, so every individual action is surfaced to you for approval and lands in the audit log on its own row. | Tool | Inputs | What it does | |---|---|---| | `list_affiliates` | `cursor?`, `take?` (1 to 200, default 50), `campaignId?` | List affiliates across your account. Cursor-paginated. Filter by campaign. | | `get_affiliate` | `id` | Fetch one affiliate's full detail (totals, recent conversions, notes). | | `list_conversions` | `cursor?`, `take?`, `affiliateId?`, `campaignId?`, `status?` (`Pending`/`Approved`/`Paid`/`Refunded`/`Denied`/`Invalidated`), `kind?` (`sale`/`lead`/`click`), `since?` (ISO8601), `subId?` (a channel tag, or `untagged`) | List conversions (sales, leads and click rewards). Cursor-paginated. `Invalidated` is the fraud-quarantine queue. | | `record_lead` | `affiliateSlug`, `email`, `name?`, `campaignId?`, `subId?` | Record a free signup as a lead (an email tied to the referral, before payment), so a later sale is still credited even if the click cookie is gone. For Stripe, Paddle, Chargebee, Recurly, and Creem, leads are captured automatically; use this tool for other processors or free-account signups. Requires lead tracking to be enabled for your account. See [Track leads and signups](/docs/developers/lead-tracking). | | `list_campaigns` | `cursor?`, `take?` | List referral campaigns with commission/payout rules and affiliate count. | | `get_campaign` | `id` | Fetch one campaign with commission/payout rules and recent stats. | | `list_payouts` | `cursor?`, `take?`, `status?` (`Pending`/`Processing`/`Paid`/`Failed`), `affiliateId?`, `since?` | List payouts, one row per affiliate per run: `amountCents` + `currency` (USD on consolidated runs), `method` (`StripeConnect`, `PayPal`, or `GlobalPayouts`), status and timestamps, and per-conversion `lineItems` with the native amount, the FX rate as native major units per 1 USD, and the rate date. No batch total or recipient count: sum the rows you need. | | `get_payout` | `id` | Fetch one affiliate's payout by id with its per-conversion line items (native amount, FX rate, rate date, and any reversed amount). | | `dashboard_summary` | `range?` (`7d`/`30d`/`90d`/`ytd`/`all`, default `30d`) | At-a-glance KPIs (revenue, conversions, leads, active affiliates, pending approvals). | | `dashboard_timeseries` | `metric` (`revenue`/`conversions`/`clicks`/`leads`/`commissions`/`payouts`/`refunds`), `range?` (`7d`/`30d`/`90d`/`ytd`, default `30d`) | Daily timeseries for charting. ≤ ~365 points (longer windows bucket monthly). Money points may carry an additive `unconvertibleByCurrency` map (native minor units per currency held out of the bucket because no usable rate existed; `null` on count metrics). | | `list_audit_log` | `page?` (1-based, default 1), `pageSize?` (max 100, default 50), `action?`, `entityType?`, `since?`, `until?` | Audit entries. **Offset-paginated** (not cursor). Returns `actorType` (`user`/`api_key`/`mcp`/`system`/`admin`), `action`, `entityType`, `entityId`, `before`, `after`, `createdAt`. | | `approve_affiliate` | `id` | Approve a pending application. Sends the magic-link welcome email. | | `reject_affiliate` | `id`, `reason?` (max 1000 chars), `notify?` (default `true`) | Reject a pending application. Optionally notifies the applicant. | | `preview_payout` | `campaignId?`, `since?`, `affiliateId?` | Compute what a payout run would pay without running it: per-affiliate readiness (`payoutReady`, `blockedReason`) and per-currency native sums, `eligibleByCurrency` (all candidates) and `nativeTotalsForRun` (ready candidates, the map a run confirms). Payouts settle in USD: a run mints one USD payout per affiliate from the confirmed currencies, converting each commission at the stored run-date reference rate; `usdEstimateAtRunRateCents` is a display-only USD estimate at today's stored rates, and the currencies in `fxUnavailableCurrencies` have no fresh rate, are held out of the run, and keep accruing. Safe to call repeatedly. Actually running a payout stays dashboard-only (never possible via MCP or API keys). | | `mint_embed_token` | `affiliateId`, `expiresInHours?` (1 to 720, default 24), `allowedOrigins?` (string or array of URLs), `rotate?` | Issue a short-lived affiliate-dashboard embed token. **Growth+ only**; Starter/Trial receive `plan_tier_required` (402). | | `get_home_currency` | (no inputs) | Read the org's HomeCurrency setting plus the supported ISO 4217 allowlist + whether the caller's plan permits changing it. | | `set_home_currency` | `homeCurrency` (3-letter ISO 4217) | Change the org's HomeCurrency. **Pro only**; lower tiers receive `plan_tier_required` (402). Idempotent on identical before/after; audit-logged. Historical conversions keep their native currency. | | `get_program_sub_affiliate_config` | `programId` | Read a campaign's sub-affiliate (2-tier override, one recruiting level) settings: `{ enabled, percent }`. | | `set_program_sub_affiliate_config` | `programId`, `enabled`, `percent` (0 to 100) | Enable / disable sub-affiliate recruiting on a campaign and set the override percent. Included on every plan. Admin or Owner role + verified email required. Audit-logged. | | `list_affiliate_sub_affiliates` | `affiliateId` | Brand-side drilldown: list the sub-affiliates recruited by a specific affiliate, with each recruit's status, total commission generated, and override earned. Flat array (no pagination). | | `list_campaign_coupons` | `programId` | List all coupon codes assigned at the campaign level. | | `create_campaign_coupon` | `programId`, `name`, `discountType` (`PercentOff`/`AmountOff`), `discountValue`, `discountCurrency?`, `duration`, `durationInMonths?`, `maxRedemptionsPerCode?`, `expiresAt?`, `selfMintMode?` (`Disabled`/`AllAffiliates`/`SpecificAffiliates`; legacy `allowAffiliateSelfMint?`), `grantedAffiliateIds?`, `maxCodesPerAffiliate?`, `productRefs?` (Stripe `prod_` ids, max 100), `overridesClickAttribution?`, `commissionOverrideType?` + `commissionOverrideValue?` (flat rate for codes minted off this coupon), `choices?` (2-4 discount/commission splits the affiliate picks from at mint time) | Create a campaign-level coupon. Commission override and choices are mutually exclusive. | | `list_affiliate_coupons` | `affiliateId` | List per-affiliate coupon codes for an affiliate. | | `list_my_coupons` | (no inputs) | Affiliate-side: list the calling affiliate's self-mintable campaigns and existing per-affiliate coupon codes. Resolves the caller by user identity, which a brand connection token does not carry, so on a standard brand MCP connection this tool returns an empty result. | | `generate_affiliate_coupon` | `affiliateId`, `campaignCouponId`, `code?`, `choiceId?` (required for choice coupons) | Mint a per-affiliate coupon code for an affiliate on a self-mint-enabled campaign. | | `revoke_affiliate_coupon` | `affiliateId`, `couponId` | Revoke (deactivate) a per-affiliate coupon code. | | `get_org_profile` | (no inputs) | Read the calling org's profile: name, slug, brand color, logo, description, plan tier, subscription status, trial end, payout grace days. Useful as a "who am I?" check at the start of any assistant conversation. | | `get_billing_state` | (no inputs) | Read the org's billing state. Returns `planTier`, `subscriptionStatus`, `trialEndsAt`, `stripeCustomerLinked`, `stripeConnectLinked`, plus a plan-limits snapshot. The `stripeConnectLinked` boolean is the deciding factor for install path (Stripe-webhook vs pixel vs S2S). | | `list_install_platforms` | (no inputs) | List every platform with a Rekomi install recipe (41 today). Returns `platformKey`, `displayName`, `category`, `conversionFire` (`stripe-webhook`, `s2s`, `crm-lookup` for platforms whose CRM connection credits sales, or the legacy `browser-pixel`), `requiresStripeConnect`, `requiresApiKey`. Use this to map the user's "I use Webflow" to a platform key. | | `get_install_recipe` | `platformKey` | Fetch the full structured install recipe for one platform: ordered steps, common gotchas, conversion-fire path, and the canonical docs URL. Unknown keys return a generic 4-path fallback recipe (processor webhook, coupon codes, S2S, or the platform connection) instead of failing. | | `create_campaign` | `name`, `slug`, `description?`, `termsUrl?`, `defaultCommissionType?` (`Percentage`/`Fixed`), `defaultCommissionValue?`, `defaultCommissionModel?` (`Cps`/`Cpc`/`Cpl`; legacy `Cpa`/`RevShare`/`Hybrid` still accepted), `cookieWindowDays?`, `attributionModel?`, `autoApprove?`, `networkVisibility?`, `budgetCents?` (cap on click + lead spend for any campaign that pays per click or per lead, in minor units of the org home currency; strongly recommended, without it spend is uncapped; at most USD 100,000,000 equivalent at the stored rate, else `400 budget_out_of_range`), `referralDestinationUrl?` (Cpc redirect target), `minPayoutCents?`, `leadBountyCents?` (per-lead leg on any primary except Cpl; minor units of the org home currency, USD 0.10 to USD 1,000 equivalent at the stored rate, else `400 lead_bounty_out_of_range`; never clamped), `clickRewardCents?` (per-click leg on any primary except Cpc; minor units, USD 0.01 to USD 1,000 equivalent, else `400 click_reward_out_of_range`; makes the campaign invite-only), `saleCommissionType?` + `saleCommissionValue?` (per-sale leg on a Cpc/Cpl primary; renewals follow `recurringMonths`), `fraudStrictness?` (`Strict`/`Standard`/`Relaxed`, campaigns with a per-click or per-lead leg only), `applicantScreening?` (`Off`/`Low`/`Medium`/`High`), `requireReviewOnVpn?`, `adTrafficPolicy?` (`Allow`/`Review`/`Block`), `showLeaderboard?`, `allowedCountries?` (ISO alpha-2 list), `recurringMonths?`, `recurringDelayInvoices?` | Create a new campaign (referral program). Backend enforces commission type/model pairing (RevShare requires Percentage type, Cpc/Cpl require Fixed). A `Fixed` `defaultCommissionValue` is bounded in USD-equivalent terms at the stored rate: per-click / per-lead fees at most USD 1,000 (`400 unit_fee_out_of_range`), per-sale flat amounts at most USD 1,000,000 (`400 fixed_commission_out_of_range`). Audit-logged. | | `get_budget_status` | `campaignId` | Read a campaign's click + lead spend-cap status: total budget, spent, and remaining (minor units of the org home currency). Unlimited if no budget set. Spend whose currency has no usable stored rate is held out of `spentCents` and listed in `fxUnconvertibleByCurrency` (native minor units per code), the authoritative figure; `fxUnconvertibleCents` is a sum of native minor units across currencies, a presence signal rather than a money amount. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. | | `list_fraud_events` | `campaignId?`, `status?` (`open`/`all`) | List fraud signals flagged on paid clicks and leads (already dropped, no payout): high-risk IPs, bots, self-leads, disposable emails, per-IP-cap hits, abusive affiliates. The review queue. Applies to per-click and per-lead campaigns. | | `review_fraud_event` | `id`, `action` (`resolve`/`dismiss`) | Review a fraud signal: mark resolved or dismiss as a false positive. Manager role. A write tool: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. | | `ban_affiliate` | `affiliateId` | Ban a partner: stops all their future clicks, leads, and conversions from earning. Manager role. Destructive: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. | | `validate_conversion` | `id` | Release a fraud-quarantined (`Invalidated`) conversion back to payable (clear a false positive). Manager role. A write tool: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. | | `invalidate_conversion` | `id` | Quarantine a `Pending`/`Approved` conversion you judge fraudulent (sets `Invalidated`, not paid). Manager role. Withholds payment: confirm first. Limited preview: applies to per-click and per-lead campaigns, which are rolling out gradually. | | `create_affiliate_invite` | `campaignId`, `email`, `fullName?`, `slug?`, `personalNoteHtml?`, `customCommissionType?`, `customCommissionValue?`, `autoApprove?` (default `true`) | Send a branded invite email to one affiliate for a campaign. Available on every plan including Trial. Personal note HTML is server-sanitized to a strict allowlist. **MIGRATION**: pass `slug` to preserve the affiliate's existing slug from a prior platform so their existing public referral URL keeps working. Returns 409 `slug_already_taken` on collision. | | `bulk_invite_affiliates` | `campaignId`, `recipients[]?` (preferred) OR `emails[]?` (legacy, 1-50), `personalNoteHtml?`, `autoApprove?` | Bulk-invite up to 50 affiliates at once. **Requires Starter tier or higher with an active paid subscription** (Trial users receive `paid_plan_required` HTTP 402). The `recipients` shape carries per-row `email`, `slug?`, `fullName?` so migration imports preserve every affiliate's existing slug. Response includes `slugConflicts[]` for rows where the requested slug was rejected; those still get an invite with a fresh random slug. | | `import_affiliates` | `campaignId`, `affiliates[]` (1-1000; per row `email`, `fullName?`, `status?`, `paypalEmail?`, `slug?`), `includeStatuses?`, `notifyAffiliates?`, `notificationMessageHtml?` | Bulk-import an affiliate roster from canonical JSON rows (platform-agnostic; you map any export into Rekomi fields). Dedupes by email within the campaign and the batch (idempotent). `status` defaults to `approved`. Starter+ tier for direct API-key callers (402 plan_tier_required); MCP-relayed calls bypass the plan gate. `notifyAffiliates` additionally needs Growth+ with an active subscription. Returns a revertible `jobId` + `{ imported, skipped, errored, reasons }`. A write tool: confirm before running. | | `preview_conversion_import` | `campaignId`, `conversions[]` (1-1000; canonical rows) | **Dry-run** a historical conversion/commission import. Writes nothing. Returns `{ rowsTotal, wouldImport, wouldSkip, wouldError, skipReasons, paidBalance, unpaidBalance }`. Always run this before `import_conversions` and show the user `unpaidBalance` (how much would become payable). | | `import_conversions` | `campaignId`, `conversions[]` (1-1000; per row `affiliateEmail?`/`affiliateSlug?` [one required], `customerEmail?`, `customerName?`, `amountCents`, `commissionCents`, `currency`, `occurredAt`, `paid`, `type?`, `stripeCustomerId?`, `stripeSubscriptionId?`, `externalId?`), `confirmUnpaidPayout?` (default `false`), `excludePaid?` | Import historical conversions/commissions. Idempotent (re-running never double-pays). **Money safety:** paid rows lock as settled history; unpaid rows become **payable only when `confirmUnpaidPayout=true`** (defaults false). Starter+ tier for direct API-key callers (402 plan_tier_required); MCP-relayed calls bypass the plan gate. A write tool that can create payable balances: always preview first, then confirm with the user before setting `confirmUnpaidPayout=true`. Returns a revertible `jobId` plus `unpaidConfirmed` (payable commission per currency as `{ currency, commissionCents, count }`, empty unless confirmed) and `unpaidConfirmedCurrency` (set only when exactly one currency became payable); `unpaidConfirmedCents` is a raw cross-currency sum kept for older callers and is a money amount only when `unpaidConfirmedCurrency` is set. | | `get_attribution_params` | (no inputs) | Read the org's custom attribution params + the 21 canonical defaults + the effective priority order the loader uses. Useful for confirming whether a migrating brand needs custom-param config or whether their source platform is already covered by the defaults. | | `update_attribution_params` | `custom?` (comma-separated string, max 10 entries) | Set the brand's custom attribution params. Each entry `[a-z0-9_-]{2,32}`, max 10 entries, max 256 chars total. Empty / omit clears the override. Admin-or-Owner write (audit-logged). | | `update_campaign` | `campaignId` + any of: `name?`, `description?`, `termsUrl?`, `isActive?`, `defaultCommissionType?`, `defaultCommissionValue?`, `defaultCommissionModel?`, `cookieWindowDays?`, `recurringMonths?` (`-1` clears to lifetime), `recurringDelayInvoices?`, `attributionModel?`, `autoApprove?`, `networkVisibility?`, `isPubliclyDiscoverable?`, `referralDestinationUrl?` (`""` clears), `budgetCents?` (`0` clears; otherwise the same USD 100,000,000 equivalent ceiling as create), `minPayoutCents?` (`0` clears), `leadBountyCents?` (`0` clears; otherwise the same USD 0.10 to USD 1,000 equivalent band as create), `saleCommissionType?` + `saleCommissionValue?` (`0` clears the pair), `fraudStrictness?`, `applicantScreening?`, `requireReviewOnVpn?`, `adTrafficPolicy?`, `showLeaderboard?`, `allowedCountries?` (`[]` clears) | Update an existing campaign. Omitted fields are unchanged. A write tool: confirm first. | | `update_affiliate` | `id` + any of: `fullName?`, `status?` (`Pending`/`Approved`/`Rejected`/`Paused`/`Banned`), `customCommissionType?` + `customCommissionValue?`, `clearCustomCommission?`, `notes?`, `tags?` | Update one affiliate: status, per-affiliate commission override, private notes, tags. A write tool: confirm first. | | `list_affiliate_applications` | `campaignId?`, `take?` (default 200, max 500) | The pending-approval queue, including each applicant's answers (website, audience, why-interested). Flat array. Pair with `approve_affiliate` / `reject_affiliate`. | | `create_conversion` | `campaignId`, `affiliateId`, `kind?` (`sale`/`bonus`), `amountCents?`, `currency?`, `commissionCents?`, `customerEmail?`, `occurredAt?` (≤ 5 years back), `note?`, `subId?` | Manually record an off-platform sale or a one-off affiliate bonus. Server caps: `amountCents` and a bonus `commissionCents` at most 1,000,000,000,000 minor units of the currency (a currency-blind sanity bound, the same in every currency); a sale `commissionCents` at most 5× `amountCents`. No built-in dedup. Creates a payable balance: always confirm first. | | `dashboard_leaderboard` | `metric` (`clicks`/`conversions`/`earnings`/`epc`), `limit?` (max 50), `range?`, `campaignId?` | Rank affiliates over a window. | | `dashboard_traffic_sources` | `range?`, `campaignId?`, `affiliateId?` | Where clicks come from, channel-classified: each of the top-20 sources carries a `channel` (`paid`/`search`/`social`/`email`/`ai`/`signin`/`onsite`/`portal`/`bot`/`referral`/`direct`) plus count and percentage. | | `get_insights` | `module` (`overview`/`sale-origin`/`top-products`/`geography`/`devices`/`utm`/`time-patterns`/`time-to-convert`/`funnel`/`attribution`/`recurring`/`customers`), `range?`, `campaignId?`, `affiliateId?`, `dimension?` (utm only: `source`/`medium`/`campaign`/`subid`) | Deep-dive analytics, one module per call. PII-free aggregates, top-20 capped. Money is FX-normalized into the home currency; rows with no usable stored rate are held out and, on `overview`, listed per currency in native minor units in `fxUnconvertibleByCurrency` (revenue) and `fxUnconvertibleCommissionByCurrency` (commission), the authoritative figures; `fxUnconvertibleCents` is a sum of native minor units across currencies, a presence signal rather than a money amount. | | `get_fraud_summary` | `campaignId`, `days?` (1-365, default 30) | Aggregate fraud stats for a campaign that pays per click or per lead: blocked clicks by reason, held clicks, quarantined leads, and estimated commission not billed. | | `list_uncredited_conversions` | `status?` (`open`/`dismissed`/`all`), `affiliateId?` | Sales that arrived attributed to an affiliate who could not earn at that moment (pending/paused/rejected/banned at sale time), with amounts and reasons. The "did we miss paying anyone?" queue. | | `dismiss_uncredited_conversion` | `id` | Mark an uncredited-conversion row reviewed (rightly not credited). Moves no money. | | `get_tracking_status` | `campaignId` | Install verification: whether the campaign has ever recorded a click or conversion, last-seen timestamps, and 30-day totals, including `creditedByPlatform30d` (sales credited through a CRM connection's lookup). The per-email lookup can answer `attributed_by_platform`. Call after an install walkthrough to confirm tracking works. | | `list_crm_integrations` | (no inputs) | Read the brand's connected CRM platforms with both switches: `enabled` (Sync affiliates), `salesAttribution` (`none`, `native` or `custom_field`), `salesAttributionEnabled` (Credit sales), its status and last check, the lifetime count of sales credited through the platform, and whether the signup webhook is registered. Read-only. Call it before recommending an install path on beehiiv, Kit, MailerLite, Ontraport or GoHighLevel. | | `list_deals` | none | Creator deals with their milestone schedules (status, amounts, fee snapshots, proof-of-delivery links, terms, open disputes, change orders). Funding and releasing stay in the dashboard. | | `get_deal` | `id` | One deal with its full schedule, disputes, change orders, and terms. | | `approve_milestone` | `dealId`, `milestoneId` | Approve a delivered milestone (Funded or Submitted to Approved). Moves no money; the brand releases from the dashboard or it auto-releases at the end of the review window. | | `request_milestone_changes` | `dealId`, `milestoneId`, `note?` | Ask the creator for changes on a submitted milestone; pauses its auto-release clock until they resubmit (5 rounds max). | | `get_install_snippet` | `campaignId?`, `withPixel?`, `emailCapture?` | Get the ready-to-paste install snippet for the brand with program id + custom attribution params + optional pixel public key already embedded. Email capture is on by default: the tag carries `data-rkmi-email-capture="auto"` and the tool prints next to it what that records (an email a referred visitor types into any email or text field on the pages where the tag runs; nothing without a referral) and how to turn it off; `emailCapture=false` renders the bare tag. Single source of truth for snippet generation; agent hands the response verbatim to the user. Defaults to the newest active campaign, no pixel. Returns 409 when `withPixel=true` and the org hasn't provisioned a pixel public key. | Cursor-paginated list responses always look like `{ "items": [...], "nextCursor": "…", "hasMore": true }`. `list_audit_log` returns `{ "page", "pageSize", "total", "items" }` instead. The assistant calls each tool individually and surfaces its result back to you in chat. Destructive or money-affecting tools (`approve_affiliate`, `reject_affiliate`, `import_affiliates`, `import_conversions`) should be confirmed by the user before the assistant runs them. For a conversion import, always run `preview_conversion_import` first and review the unpaid balance before authorizing payment with `confirmUnpaidPayout`. ## Install Rekomi with the assistant The most common use of the MCP server is what it sounds like: "help me install Rekomi on my site." A typical session looks like this. 1. You start a chat with Claude (or Cursor, or any MCP-capable client) and say "Help me install Rekomi on my Shopify store." 2. The assistant calls `get_org_profile` to confirm who you are and what plan you're on. 3. It calls `get_billing_state` to see whether you've connected Stripe yet. If `stripeConnectLinked` is true, it recommends the stripe-webhook path; if false, it nudges you to a native gateway integration, coupon codes, or S2S. On beehiiv, Kit, MailerLite, Ontraport or GoHighLevel it calls `list_crm_integrations` first, because the platform connection with Credit sales on is the install there. 4. It calls `list_install_platforms` and matches "Shopify" to `shopify`. 5. It calls `get_install_recipe` with `platformKey: shopify` and walks you through the steps in chat. It surfaces the gotchas inline (the native Rekomi Shopify app is the no-code path; on the manual fallback, Shopify Payments hides your Stripe and the Custom Pixel sandbox cannot load external scripts; Shopify Collabs is a separate product). 6. If you haven't created a campaign yet, the assistant offers to call `create_campaign` and asks you to confirm the name, slug, commission type, and value before doing so. 7. If you're migrating from another platform, prefer the dedicated importers under Settings, Migrate - there are 22 (Rewardful, Affonso, Tapfiliate, FirstPromoter, PartnerStack, Dub Partners, Tolt, Lemon Squeezy, Gumroad, impact.com, PromoteKit, LeadDyno, Trackdesk, Partnero, UpPromote, GoAffPro, ReferralCandy, Refersion, BixGrow, Shopify Collabs, Everflow, and Post Affiliate Pro) (the Rewardful, FirstPromoter, and Tolt API pulls also bring leads, lifetime click counts, and commission history; Gumroad's sales history pulls from its API after the roster CSV). For an assistant-driven flow, paste your existing affiliate emails into the chat. The assistant uses `bulk_invite_affiliates` to send branded onboarding emails to all of them at once. (Requires Starter tier or higher with an active paid subscription.) ### What the assistant cannot do (security) For the install flow to be safe, the MCP server intentionally does not expose certain operations as tools: - **The assistant cannot mint API keys.** If your install needs a `rk_live_*` bearer + `rks_*` signing secret (Gumroad / Rails / Django / any S2S path), the assistant will deep-link you to **Settings → API keys** to mint one yourself, then ask you to paste the key back. The MCP token cannot escalate into a long-lived API key that outlives the MCP token itself. - **The assistant cannot create new MCP tokens.** Token minting is owner-only and JWT-only. - **The assistant cannot run, retry, or reverse payouts.** Money movement requires an interactive dashboard session; MCP and API keys both receive 403 `interactive_session_required`. The assistant can only `preview_payout`. - **The assistant cannot change billing or open Stripe Checkout sessions.** Billing decisions stay with the human owner; the assistant can only read billing state via `get_billing_state`. If a tool seems missing, that's the reason. The capability is intentionally not exposed. ## Plan-gate semantics There are two different plan gates in the Rekomi backend: - **Public-API gate**: applies per endpoint to direct API-key (`rk_live_*`) callers. Most of the `/api/v1/*` surface requires Starter or higher, which every current plan meets; a few endpoints such as audit-log export require Pro. Calls relayed through the MCP server **bypass** this gate entirely, so every MCP tool works on any plan. - **Feature-tier gate**: blocks lower plans from a specific feature regardless of how it's called. MCP does **not** bypass this. Today this applies to `mint_embed_token` (Growth+) and a handful of other surfaces; a Starter user calling `mint_embed_token` over MCP gets `plan_tier_required` (HTTP 402). If a tool returns `plan_tier_required`, the answer is to upgrade the plan, not to change tokens. ## Security model - **Token shape**: `rk_mcp_*`, 256-bit random body, base64url-encoded. Stored as SHA-256 hash; plaintext never persisted. - **One-time reveal**: the full token is shown only at creation or rotation. Rotate to invalidate the old value. - **Owner-only minting**: only an organization owner can create, rotate, or revoke MCP tokens. - **Per-org cap**: 10 active tokens per organization. - **Read+write scope**: every MCP token is granted the `read_write` scope; the per-tool user-consent loop in your AI client is the user-facing approval gate. - **Direct use blocked**: a stolen `rk_mcp_*` is useless without the trusted relay. Direct hits to `api.rekomi.com` are rejected with a generic 401 `unauthorized`; only requests through `mcp.rekomi.com` carry the signed relay header the API requires. - **Relay binding**: the relay HMAC is computed over `{t}.{METHOD}.{path}.{body_sha256_hex}` with a shared secret. ±300 s timestamp window, constant-time compare. Replays, body tampering, and method/path swaps all fail validation. - **Revoke propagation**: revoked tokens stop authenticating within ~10 s across all instances (bounded by the auth cache TTL). - **Audit trail**: every tool call lands in your audit log with `actor_type = mcp`, the calling token id, action, entity, and timestamps. Filter by `actor_type = mcp` under **Settings → Audit Log**. - **Suspension gate**: a suspended account stops every MCP call within seconds. - **PII scrubbing**: tokens and the relay-identity header are scrubbed from MCP-server-side error reporting before they leave the host. ## Best practices - Use a separate token per device or per AI client. If a laptop is lost, revoke just that token. - Set up read-only assistant agents by instructing the assistant not to call write tools (the twenty-six writes are `approve_milestone`, `request_milestone_changes`, `approve_affiliate`, `reject_affiliate`, `preview_payout`, `mint_embed_token`, `set_home_currency`, `set_program_sub_affiliate_config`, `create_campaign_coupon`, `generate_affiliate_coupon`, `revoke_affiliate_coupon`, `create_campaign`, `update_campaign`, `update_affiliate`, `create_conversion`, `create_affiliate_invite`, `bulk_invite_affiliates`, `update_attribution_params`, `import_affiliates`, `import_conversions`, `record_lead`, `review_fraud_event`, `ban_affiliate`, `validate_conversion`, `invalidate_conversion`, `dismiss_uncredited_conversion`). The audit log still records the actor as the assistant. - Watch your audit log under **Settings → Audit Log** during the first few days. Filter by `actor_type = mcp` to see exactly what the assistant has done. - Don't paste the same token into multiple clients you can't independently revoke later, one token per surface keeps blast radius bounded. ## Troubleshooting - **401 `unauthorized` on a direct API call**, your AI client is hitting `api.rekomi.com` directly with an MCP token; direct calls are rejected with the generic 401. Point it at `https://mcp.rekomi.com/api/mcp` instead. - **`plan_tier_required` (HTTP 402) on a specific tool**, the tool is feature-gated to a higher plan (e.g. `mint_embed_token` is Growth+). Upgrade the plan or switch to a tool that's available on your tier. Switching tokens will not help, feature gates apply to MCP traffic too. - **`unauthorized` (HTTP 401) on every call**, the token is wrong, revoked, or rotated. Mint a fresh one under **Settings → AI assistant**. - **Tools list works but every tool returns 401**, the backend cannot validate the relay header. Most often a stale relay secret on one side; contact support. - **Claude Code still sends the old token after I rotated**, Claude Code stores the bearer header statically in `~/.claude.json`. Rotating the token in the Rekomi dashboard does not propagate. Run `claude mcp remove rekomi` and then `claude mcp add …` again with the new token. - **Claude Code logs `SDK auth failed: JSON Parse error`**, harmless. Some MCP clients probe `/.well-known/oauth-protected-resource`; the server returns a clean JSON 404 and continues with bearer auth. - **`/mcp` "Re-authenticate" doesn't do anything**, that menu item handles OAuth-flow servers; Rekomi uses a static bearer header. Use `claude mcp remove` + `claude mcp add` to swap credentials. ## See also - [Public API reference](/docs/developers/overview) - [Audit log](/docs/brands/settings#audit-log) - Machine-readable manifest: [rekomi.com/docs/developers/mcp.md](https://rekomi.com/docs/developers/mcp.md) --- # OAuth 2.0 Source: https://rekomi.com/docs/developers/oauth > Authorization Code + PKCE flow for third-party app integrations. --- title: OAuth 2.0 description: Authorization Code + PKCE flow for third-party app integrations. --- Rekomi runs a standards-compliant OAuth 2.0 authorization server at `api.rekomi.com/connect/*`. Build a "Connect to Rekomi" button into your app and your users delegate access to their Rekomi account without copy-pasting an API key. OAuth 2.0 is preferred over [API keys](/docs/developers/authentication) when: - You are building an integration that distributes to multiple Rekomi brands (not just your own org). - You want the user to pick their own scope (read vs read_write) at consent time. - You need refresh-token rotation rather than long-lived static keys. - You need your tokens to be revocable from the user's Rekomi dashboard. If you are only integrating with your own organization, API keys remain the simpler path. ## What you get | Spec | Status | | ---- | ------ | | Authorization Code flow + PKCE (S256) | Mandatory | | Refresh tokens with rotation + reuse detection | Yes | | Token revocation (RFC 7009) | Yes | | Token introspection (RFC 7662) | Yes | | Reference tokens (opaque, DB-stored, revocable) | Yes | | Issuer claim in authorization response (RFC 9207) | Yes | Public clients (browser-only apps) and confidential clients (server-side apps with a stored secret) are both supported. PKCE is required for every client regardless of type. ## Endpoints | Endpoint | Method | Purpose | | -------- | ------ | ------- | | `https://api.rekomi.com/connect/authorize` | GET / POST | Start the authorization flow. Redirects browser to the consent screen. | | `https://api.rekomi.com/connect/token` | POST | Exchange code for tokens or rotate a refresh token. | | `https://api.rekomi.com/connect/revoke` | POST | Revoke an access or refresh token (RFC 7009). | | `https://api.rekomi.com/connect/introspect` | POST | Validate a token + read its scopes / subject / expiry (RFC 7662). | Staging: replace `api.rekomi.com` with `api-staging.rekomi.com` in every URL above. The two environments have entirely separate client registrations, tokens, and authorizations. ## Register your client To register a client, contact support@rekomi.com with: - Your app's display name (rendered on the consent screen). - Your app's logo URL (https only). - Client type: `confidential` (server holds a secret) or `public` (browser app). - Redirect URIs: one or more https URLs the user is sent back to after consent. Wildcards are not permitted; exact-match enforcement per RFC 6749 Section 3.1.2. - Requested scopes: `read`, `read_write`, and/or `offline_access`. You receive a `client_id` and (for confidential clients) a `client_secret`. The plaintext secret is shown EXACTLY ONCE; store it immediately. If lost, request rotation from support. Self-service client registration is on the roadmap. Until then, manual registration keeps the consent screen free of low-quality / spammy "apps" users would have to learn to distrust. ### First-party clients Rekomi maintains its own registered OAuth clients for first-party integrations. The first one is **Zapier** (the consent screen shows the app name Zapier), the no-code automation surface that lets brands wire Rekomi into 8,000+ other apps. End users connect it the same way they would connect any third-party OAuth app: click Connect in Zapier, get redirected to the consent screen at `app.rekomi.com/oauth/authorize`, pick an org, click Allow. There is nothing to install or configure on the brand side beyond the consent click. If you are an end user trying to connect Zapier (not a developer building your own OAuth client), skip the rest of this page and head to the [Zapier integration guide](/docs/developers/zapier). ## Scopes Three scopes today: - `read`, view campaigns, affiliates, conversions, payouts, account profile. No mutations. - `read_write`, everything in `read`, plus create campaigns, approve affiliates, mint coupons, issue payouts, modify account settings. - `offline_access`, request this alongside `read` or `read_write` to receive a refresh token. Without it, the token response contains an access token only. Hierarchy: `read_write` satisfies any `read` requirement. `read` does NOT satisfy a `read_write` requirement. Request the narrowest scope your integration actually needs; users see the requested scope set on the consent screen and we recommend offering them the choice to narrow further. Only an organization Owner can approve a `read_write` grant; members with lower roles can approve read-only access. A non-Owner attempting to grant `read_write` receives 403 `insufficient_role`. ## The flow ### 1. Send the user to the authorization endpoint ``` GET https://api.rekomi.com/connect/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.example.com/oauth/callback &scope=read_write offline_access &state=A_RANDOM_STRING_TO_VERIFY_LATER &code_challenge=BASE64URL_S256_HASH_OF_VERIFIER &code_challenge_method=S256 ``` `code_challenge` is the SHA-256 hash of a random 43-128 character `code_verifier` you generate, then base64url-encoded with no padding. Save the verifier for step 3. `state` is your CSRF defense; we mirror it back to your callback verbatim, and you must reject the callback if the value does not match what you sent. The browser is redirected through the consent screen at `app.rekomi.com/oauth/authorize`. The user signs in if they are not already; the consent screen shows the organization the grant will be bound to (the currently active one), along with the app identity and requested scopes. To authorize a different organization, the user switches organizations in the dashboard first, then restarts the flow. They then click Allow or Deny. ### 2. Handle the callback After consent, the user is redirected back to: ``` https://yourapp.example.com/oauth/callback ?code=ONE_TIME_AUTHORIZATION_CODE &state=A_RANDOM_STRING_TO_VERIFY_LATER &iss=https://api.rekomi.com ``` Verify `state` matches what you sent in step 1. Verify `iss` is `https://api.rekomi.com` (or your staging issuer). If either check fails, abort the flow. If the user clicked Deny, you receive: ``` ?error=access_denied &state=A_RANDOM_STRING_TO_VERIFY_LATER ``` ### 3. Exchange the code for tokens POST to the token endpoint within 60 seconds (codes expire that fast): ```http POST /connect/token HTTP/1.1 Host: api.rekomi.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=ONE_TIME_AUTHORIZATION_CODE &redirect_uri=https://yourapp.example.com/oauth/callback &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET &code_verifier=THE_VERIFIER_FROM_STEP_1 ``` Response: ```json { "access_token": "an-opaque-reference-token-256-bits", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "another-opaque-reference-token", "scope": "read_write" } ``` `access_token` is a reference token, NOT a JWT. It is opaque. Store it as-is; do not attempt to decode. ### 4. Call the Rekomi API ```http GET /api/v1/programs HTTP/1.1 Host: api.rekomi.com Authorization: Bearer ACCESS_TOKEN_FROM_STEP_3 ``` The bearer authenticates AND identifies the organization the user granted consent for. You do NOT need to send a separate organization header; the org binding was locked at consent time and is enforced on every request. If the user revokes your authorization from `app.rekomi.com/dashboard/settings/connected-apps`, the next API call returns `401 Unauthorized`. Refresh-token attempts that follow will also `401`. Before any app has been authorized, that page shows an empty state; once an integration connects, it appears there with a revoke control. ![The Connected Apps settings page in its empty state, shown before any OAuth integration has been authorized](https://rekomi-uploads-prod.nyc3.digitaloceanspaces.com/docs/screenshots/developers/settings-connected-apps.png?v=3) ### 5. Refresh tokens Access tokens expire after 1 hour. When you get a `401`, rotate the refresh token: ```http POST /connect/token HTTP/1.1 Host: api.rekomi.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=YOUR_REFRESH_TOKEN &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ``` Response shape matches step 3. **Important**: the response includes a new `refresh_token`. The old one is immediately revoked. Reusing an old refresh token causes the entire token family to be revoked (Rekomi follows the OAuth 2.0 Security Best Current Practice draft on this). After family revocation, the user must reconnect from scratch. Refresh tokens last 30 days. Active integrations (refreshing within the 30-day window) effectively never need user reauthorization. ### 6. Revocation If your integration is uninstalled, revoke any tokens you hold: ```http POST /connect/revoke HTTP/1.1 Host: api.rekomi.com Content-Type: application/x-www-form-urlencoded token=ACCESS_OR_REFRESH_TOKEN &token_type_hint=access_token &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ``` A `200` response means the token is no longer valid. Per RFC 7009, you always get a `200` if your client credentials are valid; the response body is empty. ## Errors | HTTP | OAuth `error` | Meaning | | ---- | ------------- | ------- | | 400 | `invalid_request` | Missing or malformed parameter. | | 400 | `invalid_grant` | Code expired, already used, or refresh token revoked. | | 401 | `invalid_client` | client_id or client_secret wrong. | | 401 | `invalid_token` | Access token revoked or expired. Refresh and retry. | | 400 | `unsupported_grant_type` | Only `authorization_code` + `refresh_token` are supported today. | | 400 | `unauthorized_client` | client_id not allowed to use this grant type. | | 400 | `invalid_scope` | Requested scope is not registered for this client. | ## Token lifetimes | Token | Default lifetime | Notes | | ----- | ---------------- | ----- | | Authorization code | 60 seconds | Single-use. Exchange within the window. | | Access token | 1 hour | Reference token. Opaque. Revocable. | | Refresh token | 30 days | Reference token. Rotates on every use. | Lifetimes are currently the same for every client; contact support if your compliance needs differ. ## Security expectations - **PKCE is mandatory.** Public and confidential clients alike. `code_challenge_method=S256` only. - **Exact redirect_uri match.** No wildcards, no port flexibility, no scheme tolerance. - **Reference tokens, not JWTs.** Tokens are opaque IDs. Do not try to decode. - **Refresh rotation with family revocation.** Reuse of a rotated refresh token revokes every token in the family. - **Revocation is immediate.** When a user clicks Revoke in their dashboard, the next API call from your integration fails. - **No PII in URLs.** Tokens NEVER appear in query strings. The authorization endpoint takes the OAuth params (which are not secret) only. - **Client secrets hashed at rest.** We never store or log the plaintext. - **Tenant binding at consent time.** A token granted in org A cannot read org B even if the same user is a member of both. ## Worked example: minimal Node client ```js import crypto from "node:crypto"; import { URL, URLSearchParams } from "node:url"; const CLIENT_ID = process.env.REKOMI_CLIENT_ID; const CLIENT_SECRET = process.env.REKOMI_CLIENT_SECRET; const REDIRECT_URI = "https://yourapp.example.com/oauth/callback"; function base64url(buf) { return Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } // Step 1: build the authorize URL export function buildAuthorizeUrl() { const codeVerifier = base64url(crypto.randomBytes(64)); const codeChallenge = base64url(crypto.createHash("sha256").update(codeVerifier).digest()); const state = base64url(crypto.randomBytes(32)); const url = new URL("https://api.rekomi.com/connect/authorize"); url.searchParams.set("response_type", "code"); url.searchParams.set("client_id", CLIENT_ID); url.searchParams.set("redirect_uri", REDIRECT_URI); url.searchParams.set("scope", "read_write offline_access"); url.searchParams.set("state", state); url.searchParams.set("code_challenge", codeChallenge); url.searchParams.set("code_challenge_method", "S256"); // Stash codeVerifier + state in the user's session for the callback to read. return { authorizeUrl: url.toString(), codeVerifier, state }; } // Step 3: exchange code for tokens export async function exchangeCode({ code, codeVerifier }) { const body = new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code_verifier: codeVerifier, }); const res = await fetch("https://api.rekomi.com/connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); if (!res.ok) throw new Error(`Token exchange failed: ${res.status}`); return res.json(); // { access_token, refresh_token, expires_in, scope, token_type } } // Step 5: refresh export async function refresh({ refreshToken }) { const body = new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }); const res = await fetch("https://api.rekomi.com/connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); if (!res.ok) throw new Error(`Refresh failed: ${res.status}`); return res.json(); } ``` ## Audit + observability Every consent grant, token issuance, refresh, dashboard revocation, and detected token reuse writes an audit row visible to organization owners under `/dashboard/settings/audit-log`. Action names you will see: - `oauth.consent.granted`, user clicked Allow on the consent screen. - `oauth.consent.revoked`, user clicked Revoke in their Connected Apps page. - `oauth.token.issued`, authorization code exchanged for tokens. - `oauth.token.refreshed`, refresh rotation succeeded. - `oauth.token.reuse_detected`, a previously-rotated refresh token was reused; the entire family was revoked. ## Comparison with API keys | Capability | API key | OAuth 2.0 | | ---------- | ------- | --------- | | Setup time | Seconds | Minutes (client registration) | | Suited for | Your own org | Multi-tenant integrations | | Token shape | `rk_live_*` static string | Opaque reference token | | Refresh | None (long-lived) | Yes (30d rotation) | | User-visible in dashboard | Settings → API Keys | Settings → Connected Apps | | User can revoke independently | Owner-only | Yes (any time) | | Scope selection | At creation | At consent time | | Per-user attribution | No (org-level) | Yes (Clerk user is the Subject) | Pick API keys for your own internal automation. Pick OAuth 2.0 when distributing to other brands. --- # API overview Source: https://rekomi.com/docs/developers/overview > Base URLs, content type, errors, rate limits, and pagination. --- title: API overview description: Base URLs, content type, errors, rate limits, and pagination. --- The Rekomi API is JSON-over-HTTPS. Every request needs an API key. Every response is JSON. Every error follows the same shape. ## Base URLs - **Production**: `https://api.rekomi.com/api/v1` - **Staging**: `https://api-staging.rekomi.com/api/v1` ## Content type All requests and responses use `application/json`. UTF-8 encoded. No XML, no form-encoded bodies. ```http Content-Type: application/json Accept: application/json ``` ## Authentication Every endpoint (except a small set of public ones like `/api/p/by-handle/{handle}/{campaign}`, `/api/p/by-domain`, and `/api/v1/track/click`) requires a bearer token in the `Authorization` header: ```http Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxx ``` Generate API keys at `/dashboard/settings/api-keys`. See [Authentication](/docs/developers/authentication) for the full lifecycle (create, rotate, revoke, scope to read vs write). S2S tracking and webhook payloads have a separate HMAC signing step on top of the bearer auth. See [S2S tracking](/docs/developers/s2s-tracking) and [Webhooks](/docs/developers/webhooks). ## Errors Business-rule errors under `/api/v1` are JSON in this shape; endpoints outside `/api/v1` return compatible JSON errors but are not guaranteed to carry all three keys: ```json { "error": "plan_tier_required", "message": "White-label embed requires Growth tier or higher." } ``` - **error**: a stable string code. Use this for branching logic in your code. Codes never change once published. - **message**: a human-readable description. Subject to change. Do not parse. - **field** (optional): the field that triggered the error, when relevant. Not every failure uses this envelope. Request-validation failures (a malformed body or query parameter) return a standard problem details object with an `errors` map (`{ type, title, status, errors }`), 401 responses carry no body, and many 404s are body-less too. Common error codes: | Code | HTTP | Meaning | |---|---|---| | `unauthorized` | 401 | Missing or invalid API key | | `forbidden` | 403 | API key valid but not authorized for this endpoint | | `not_found` | 404 | Resource does not exist or is not visible to your org (often body-less) | | (problem details) | 400 | Request body or query params did not validate; returns `{ type, title, status, errors }`, not the envelope above | | `idempotency_conflict` | 409 | Same idempotency key used with different payload | | `plan_tier_required` | 402 | Feature requires a higher plan tier | | (status only) | 429 | Too many requests in the window; empty body | | `server_error` | 500 | Something broke on our end | Never parse the `message` field. Always branch on `error`. ## Rate limits Limits are partitioned per user for authenticated calls (API-key calls share the bucket of the user who created the key), and per IP for anonymous and webhook traffic: | Endpoint class | Limit | |---|---| | Standard authenticated API (reads and writes) | 600 req/min | | Write-heavy endpoints (bulk import, invites) | 6 to 10 req/min | | Public affiliate application form | 5 req/min per IP | | Anonymous tracking (click and lead capture) | 60 req/min per IP | | S2S conversion ingest (`/api/tracking/s2s`) and webhooks | 1500 req/min per IP | When you exceed the limit, the response is HTTP 429 with an empty body. Wait, then retry with exponential backoff for sustained spikes. ## Pagination The core resource lists (programs, affiliates, conversions, payouts) return cursor-based pagination envelopes; a few auxiliary lists (pending applications, fraud events, leaderboards) return flat arrays instead: ``` GET /api/v1/conversions?take=100&cursor=eyJpZCI6MTIzNH0 ``` - `take`: page size. Max 200, default 50. (One exception: `GET /api/v1/conversions` returns 100 rows when `take` is omitted.) - `cursor`: opaque cursor returned in a prior response's `nextCursor` field. Omit for the first page. Response shape: ```json { "items": [ /* up to `take` items */ ], "nextCursor": "eyJpZCI6MTIzNH0", "hasMore": true } ``` When `hasMore` is false, you have reached the end. ## Idempotency For write endpoints that create new resources, you can supply an `Idempotency-Key` header (UUID v4 recommended). Within a 60-minute window, the same key with the same payload returns the prior response. The same key with a different payload returns HTTP 409. ```http Idempotency-Key: 8f14e45f-ceea-467a-bb2c-d8b8c0afc8d8 ``` Use this for any write you might retry on network failures. ## CORS The API has restrictive CORS. The default allowlist covers Rekomi's own surfaces (`rekomi.com`, `app.rekomi.com`), every branded `{handle}.rekomi.com` subdomain, and each brand's verified custom portal domain. For server-to-server calls (Node.js, Go, Python, anything not in a browser), CORS doesn't apply, so make whatever request you like. For browser-side requests from your own domain, proxy through your application server. We don't add arbitrary partner origins beyond the Rekomi surfaces, branded subdomains, and verified custom portal domains above. The white-label embed iframe is a separate flow with its own per-token origin allowlist; see [Embed](/docs/developers/embed). ## Plan requirements Programmatic access (API keys) is gated by plan tier. The in-app dashboard at `app.rekomi.com` is unrestricted on every paid tier; only `rk_live_*` API calls are gated. The hosted MCP server is the exception: it works on every plan including Trial, since each tool call is relayed through `mcp.rekomi.com` and gated per-tool by your AI client's consent loop rather than by a direct-API plan check. | Feature | Required plan | What it includes | |---|---|---| | Hosted MCP server (`mcp.rekomi.com/mcp`) | **Trial+ (every plan)** | Connect Claude, Cursor, ChatGPT to your account. 61 tools, read and write, relay-bound tokens. | | S2S conversion tracking (`/api/tracking/s2s`) | **Starter** | Record conversions from your backend when cookies aren't viable. | | Public REST API (`/api/v1/programs`, `/affiliates`, `/conversions`, `/payouts`, `/dashboard`) | **Starter** | Full read + write programmatic access to your data, except running or retrying payouts, which require a dashboard session. | | Customer-facing webhooks (`/api/webhook-endpoints`) | **Starter** | Receive event pushes for conversions, payouts, affiliates. | | Zapier integration ([app store listing](/docs/developers/zapier)) | **Starter** | 9 triggers, 9 actions, 4 searches. Connect to 8,000+ apps without writing code. | | Embed iframe (`/api/v1/embed`) | **Growth** | Read-only affiliate earnings iframe embedded in your own site. Distinct from the Pro [white-label portal](/docs/brands/white-label). | | AI Co-Pilot | **Growth** | AI suggestions for affiliate matches + campaign copy. Dashboard-only; not callable with an API key. | | Audit log API export | **Pro** | Programmatic SIEM export of org audit trail. | Trial users get the features of the tier they picked at signup for the trial window; a Starter trial does not unlock Growth or Pro features. Plan gates fire as 402 `plan_tier_required` with the required + current tier in the body: ```json { "error": "plan_tier_required", "required": "Growth", "current": "Starter", "trialing": false, "message": "White-label embed requires Growth tier or higher. Upgrade in Settings → Billing." } ``` The dashboard (`app.rekomi.com`) authenticates via Clerk JWTs and bypasses plan gates so brand operators can always see and manage their own data regardless of tier. ## Versioning All routes live under `/api/v1`. We will introduce `/api/v2` if and when we make breaking changes. v1 stays available for at least 24 months after v2 ships. Deprecations are announced via the blog and a `Deprecation` header on responses. ## Machine-readable docs LLMs and coding agents integrating against Rekomi should fetch [`/docs/developers/reference.md`](/docs/developers/reference.md), a single-file manifest enumerating every `/api/v1/*` endpoint, request and response shape, error codes, plan gates, webhook events, signature verification steps, and rate-limit policies in plain prose with structured headings. The manifest is updated alongside API releases and is the canonical machine-consumable spec; this page and the rest of `/docs/developers/*` are the human-friendly views of the same surface. ## Where to go next - [Authentication](/docs/developers/authentication): generate keys, manage scopes. - [S2S tracking](/docs/developers/s2s-tracking): server-side conversion ingest. - [Track leads and signups](/docs/developers/lead-tracking): capture free signups (automatic for Stripe and Paddle) and email-match a later sale. - [Webhooks](/docs/developers/webhooks): receive events from Rekomi. - [Embed](/docs/developers/embed): host an affiliate's dashboard on your domain. - [MCP](/docs/developers/mcp): connect Claude, Cursor, or ChatGPT to your account via the hosted MCP server. - [Reference](/docs/developers/reference): every v1 endpoint with method, scope, and example. --- # No-code & non-Stripe checkouts Source: https://rekomi.com/docs/developers/pixel-tracking > 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. --- title: No-code & non-Stripe checkouts description: 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. --- Browser-side conversion firing was removed; browser-only conversion tracking was unreliable. `window.Rekomi.convert()` still exists but records **leads** (free signups) only, never sales. Record sales via a native processor, the [S2S API](/docs/developers/s2s-tracking), or coupon codes instead. For the `window.Rekomi` JavaScript API (reading the referral / affiliate / campaign on page load, and `convert()` for lead capture), see [Tracking script & window.Rekomi](/docs/developers/tracking-script). This is the path for **no-code platforms** whose checkout isn't a natively-tracked processor. It has two parts: - **Clicks**: paste the tracking script in your site's ``. It captures the affiliate param (`?via=`, `?ref=`, …) into `localStorage` on your domain (plus a cookie on the tracking host, which is first-party only when you use a custom tracking domain), and registers `window.Rekomi`. - **Conversions**, record the sale via a native processor (automatic), the [S2S API](/docs/developers/s2s-tracking), or [coupon codes](/docs/developers/coupons). There is no browser-side conversion call. Click tracking with the script is available on every plan; recording conversions through the S2S API requires **Starter or higher** (14-day trials accepted). ## Works with these platforms These platforms have step-by-step install guides in the [Setup checklist](/dashboard/setup/install) and are pre-verified against this two-snippet recipe: } title="WordPress" href="/docs/brands/install/wordpress" description="The Rekomi plugin installs it for you; WPCode stays documented as the no-plugin path." /> } title="Shopify" href="/docs/brands/install/shopify" description="The native Rekomi app, live on the Shopify App Store, tracks clicks and conversions natively with no relay." /> } title="Webflow" href="/docs/brands/install/webflow" description="Site settings > Custom code > Head code for the click script; track conversions via S2S." /> } title="Framer" href="/docs/brands/install/framer" description="Site settings > General > Custom Code > Start of tag (Show Advanced)." /> } title="Squarespace" href="/docs/brands/install/squarespace" description="Settings > Advanced > Code Injection > Header (Business plan or higher)." /> } title="Wix" href="/docs/brands/install/wix" description="Install the Rekomi app from the Wix App Market and tracking is automatic; the manual Custom Code paste remains only for separately connected gateways." /> } title="BigCommerce" href="/docs/brands/install/bigcommerce" description="Install the Rekomi app from the BigCommerce App Marketplace and tracking is automatic; only legacy Blueprint themes need the manual head paste." /> } title="Bubble" href="/docs/brands/install/bubble" description="Settings > SEO/metatags > Script/meta tags in Header." /> } title="Carrd" href="/docs/brands/install/carrd" description="Embed element with Location: Head (Pro Lite+) or site-wide head slot (Pro Plus)." /> } title="Memberstack" href="/docs/brands/install/memberstack" description="Goes in the host platform's head (Webflow or custom HTML), not in Memberstack itself." /> } title="Outseta" href="/docs/brands/install/outseta" description="Same as Memberstack. Install in the host site alongside Outseta's own embed code." /> } title="Teachable" href="/docs/brands/install/teachable" description="Site > Code Snippets > Global Head Snippets (Builder plan+)." /> } title="Podia" href="/docs/brands/install/podia" description="Site Settings > Analytics > Custom code > Website tracking code (footer-injected)." /> } title="Thinkific" href="/docs/brands/install/thinkific" description="Settings > Site & app settings > Code & analytics > Site Footer Code." /> } title="Ghost" href="/docs/brands/install/ghost" description="Settings > Code injection > Site Header. Ghost Pro and self-hosted." /> } title="beehiiv" href="/docs/brands/install/beehiiv" description="The beehiiv connection credits sales from beehiiv's own signup record; the Google Tag Manager tag is optional, for click stats." /> } title="Kit (ConvertKit)" href="/docs/brands/install/kit" description="The Kit connection credits sales, Kit Commerce included, from Kit's own signup record; the per-landing-page script is optional, for click stats." /> } title="Lemon Squeezy" href="/docs/brands/install/lemonsqueezy" description="Native integration: paste one API key and Rekomi creates the webhook, so conversions track automatically (the head script here still captures clicks)." /> } title="Vanilla / custom HTML" href="/docs/brands/install/vanilla-js" description="Plain HTML + a script tag in your shared header partial." /> } title="Next.js" href="/docs/brands/install/nextjs-app-router" description="App Router or Pages Router via next/script. SPA gotcha applies (see below)." /> } title="React (Vite, CRA)" href="/docs/brands/install/react" description="index.html head script for clicks; fire conversions from your backend via S2S." /> } title="Vue (Vite)" href="/docs/brands/install/vue" description="index.html head script. For Nuxt 3 use useHead in app.vue instead." /> **Don't see your platform?** The script works on any site where you can paste an HTML ` ``` Rekomi records an email a referred visitor types into any email or text field on the pages where the tag runs (signup, login, checkout and other forms) so the sale can be credited even when the checkout passes no referral. Nothing is recorded without a referral. Remove the `data-rkmi-email-capture` attribute to turn this off; explicit `Rekomi.convert()` calls still work. If you have custom params configured in **Settings > Attribution**, your install snippet also gets a `data-rkmi-params="..."` attribute that the dashboard generates automatically. Copy the exact snippet from **Dashboard > Settings > Tracking > Website snippet** (pre-filled with your real `data-program-id`, the email capture attribute and any custom params) rather than hand-editing the placeholder above. If you have a verified [custom domain](/docs/developers/custom-tracking-domain), the copied snippet loads from your own subdomain instead of `api.rekomi.com`. ## The `window.Rekomi` object The script registers `window.Rekomi`, exposing the referral slug plus the affiliate and campaign **name** on page load, so you can capture who referred a signup, client-side. See the full API (`referral`, `affiliate`, `campaign`, `ready()`, `getReferral()`, `clearReferral()`) in [Tracking script & window.Rekomi](/docs/developers/tracking-script). ## Sub-IDs Affiliates can tag one link per channel, `?via=alex&sub=yt`, instead of minting a link per channel. The script reads `sub` (also `subid` and `sub_id`) whenever it reads an attribution param, sends the tag with the click, and keeps it next to the slug in `localStorage` (`rkmi[:programId]:sub`, same expiry) together with the click id (`rkmi[:programId]:cid`). `window.Rekomi.subId` and `window.Rekomi.clickId` expose them, `convert()` sends the tag with the lead, and the click endpoint sets a `_rkmisub` cookie next to `_rkmi` (first-party on a [custom tracking domain](/docs/developers/custom-tracking-domain)). A new referred visit without a tag clears the stored one. A sale carries the tag when Rekomi can tie it to the visit exactly: the click id (Shopify, Wix, BigCommerce, WordPress, the hosted checkout door), a lead recorded with the tag, the customer's earlier tagged purchase (renewals and repeat orders), or a `rekomi_sub` value stamped on the checkout next to `rekomi_ref` (Stripe metadata, Paddle and Lemon Squeezy custom data, Chargebee meta_data or a `cf_rekomi_sub` custom field, Polar, Creem, Dodo Payments and Mollie metadata, a Braintree custom field, Gumroad `url_params`). If your code stamps `rekomi_ref` from the cookie or `window.Rekomi.referral`, stamp `rekomi_sub` from `_rkmisub` or `window.Rekomi.subId` the same way. Rekomi never guesses a tag from the link's latest click, so a sale with no exact source shows no tag. The tag is normalized to lowercase letters, numbers, dots, hyphens and underscores, at most 64 characters. ## Common gotchas **Cookie-blocking browsers (Safari ITP, Brave Shields):** the script's primary attribution store is `localStorage` on your domain, so attribution keeps working even when the tracking-host cookie is rejected (localStorage has its own block rules). A [custom domain](/docs/developers/custom-tracking-domain) makes the cookie first-party and recovers most otherwise-blocked clicks. **Single-page apps:** `window.Rekomi` is set once on the first load and persists across client-side route changes. If you read the referral context from a component that mounts after a route change, use a `ready()` callback, see the [SPA note](/docs/developers/tracking-script#single-page-apps). ## Verifying installation After pasting the script: 1. Open **Dashboard > Settings > Tracking > Website snippet** in Rekomi. 2. Under **Verify my installation**, enter the page where you installed the snippet and click **Verify**. Rekomi scans the served HTML for the loader and reports any recorded clicks. 3. To test end to end (click → conversion → commission), open one of your affiliates' real referral links, then use **Send test conversion** on the campaign's **Performance tab** (or your payment processor's test mode). Visiting a URL with `?via=test` records nothing unless `test` is a real affiliate slug; an unresolvable slug returns 404 and writes no click. If a real test click doesn't register, hard-refresh (hosts cache HTML) and re-test in an incognito window (ad blockers filter trackers). ## Lifting accuracy further: custom domain The default snippet loads from `api.rekomi.com`, which content blockers can match on. Any brand, on any plan, can swap the script host to a domain they control (e.g. `affiliates.brand.com`) via a [custom domain](/docs/developers/custom-tracking-domain). The script then loads first-party, sidestepping generic blocker rules and Safari ITP's third-party caps. The same domain also serves your affiliate signup pages, so one host covers both tracking and the portal. Realistic lift: +3-5 percentage points of conversions tracked. --- # API reference Source: https://rekomi.com/docs/developers/reference > Every v1 endpoint with request schema, response JSON example, scope, and notes, verified against the live code. --- title: API reference description: Every v1 endpoint with request schema, response JSON example, scope, and notes, verified against the live code. --- This page covers the core endpoints exposed under `/api/v1/*` for API-key-authenticated callers. Each entry shows the request shape, a realistic response JSON, the scope required, and any plan or role gates. It is not exhaustive: the coupons surface is documented separately in [Coupon code tracking](/docs/developers/coupons), and a handful of auxiliary endpoints (slug checks, per-campaign tracking status, commission summaries, payout ledger and receipts) are not listed here. Responses may also carry additional convenience fields beyond those shown (display names, customer details, avatar URLs, budget and threshold fields); treat the schemas below as the core shape, not a strict ceiling. **Not building a custom client?** Rekomi exposes the same surface through two no-code / low-code consumers: - [Zapier](/docs/developers/zapier), 9 triggers, 9 actions, 4 searches. Click-to-connect from the Zapier marketplace. Use this when your goal is to wire Rekomi into Slack, Gmail, HubSpot, Notion, Google Sheets, Typeform, or any other Zapier-supported app. - [MCP server](/docs/developers/mcp), 61 tools exposed to Claude, Cursor, ChatGPT, and other AI clients via the Model Context Protocol. Use this when you want an AI assistant to operate Rekomi for you. Both consumers are governed by the same plan gates and audit log as the raw REST surface documented below, they are alternate transports onto the same domain model, not separate APIs. **Auth header** on every authenticated endpoint: ```http Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxx ``` **Scope** column tells you which API-key scope satisfies the endpoint: - `read`: keys created with scope `read` are accepted (also satisfied by `read_write`) - `read_write`: only `read_write` keys are accepted Default inference: `GET/HEAD/OPTIONS` → `read`; `POST/PUT/PATCH/DELETE` → `read_write`. Embed token endpoints always require `read_write`. **Plan tier** required per resource family (see [Overview → Plan requirements](/docs/developers/overview#plan-requirements) for the full table): - **Starter+**: S2S tracking, Programs, Affiliates, Conversions, Payouts, Dashboard metrics, Customer webhooks, Zapier - **Growth+**: Embed iframe, AI Co-Pilot - **Pro+**: Audit log API export Plan gates fire as `402 plan_tier_required` with `required` + `current` + `trialing` in the response body. The in-app dashboard at app.rekomi.com bypasses plan gates (JWT auth path). **Common response envelopes** (defined in [Overview](/docs/developers/overview)): ```json // Cursor-paginated list (the core resource lists: programs, affiliates, conversions, payouts) { "items": [ /* ... */ ], "nextCursor": "eyJpZCI6IjAxNzg5..." , "hasMore": true } // Business-rule error { "error": "plan_tier_required", "message": "Feature requires a higher plan tier" } ``` Request-validation failures return a standard problem details object (`{ type, title, status, errors }`) instead of the business-rule envelope, and 401s and many 404s carry no body; see [Overview → Errors](/docs/developers/overview#errors). ## Programs **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. `AffiliateProgram` resources. Brands typically have 1-3 programs (e.g., one default + one with a special promo rate). ### `GET /api/v1/programs` **Scope:** `read`. List programs. Cursor-paginated. Query params: `take` (default 50, max 200), `cursor` (opaque from previous `nextCursor`). ```json { "items": [ { "id": "0789abcd-...", "name": "Default program", "slug": "default", "description": "Earn 20% recurring on every annual plan.", "termsUrl": "https://yourbrand.com/affiliate-terms", "isActive": true, "defaultCommissionType": "Percentage", "defaultCommissionValue": 20.0, "defaultCommissionModel": "Cps", "cookieWindowDays": 60, "recurringMonths": 12, "recurringDelayInvoices": 0, "attributionModel": "LastTouch", "autoApprove": false, "customDomain": null, "customDomainVerified": false, "networkVisibility": "Private", "isPubliclyDiscoverable": true, "createdAt": "2026-05-01T12:00:00Z", "updatedAt": "2026-05-12T08:30:00Z", "archivedAt": null } ], "nextCursor": null, "hasMore": false } ``` `isPubliclyDiscoverable` controls SEO visibility of the public `{handle}.rekomi.com/{campaign}` landing page. When `false`, the page emits `` and is excluded from the sitemap; flip to `false` for invite-only or sensitive-rate programs. ### `GET /api/v1/programs/{id}` **Scope:** `read`. Returns a single `ProgramResponse` (same shape as a list item above). ### `GET /api/v1/programs/stats` **Scope:** `read`. The campaigns-list rollup: an array with one entry per campaign (`programId` on each), carrying `affiliatesCount`, `clicks30d`, `conversions30d` (sales, leads and paid clicks together), `sales30d`, `leads30d`, `paidClicks30d`, `pendingCommissionCents`, `availableCommissionCents`, `settlingCommissionCents`, `owedEligibleInHomeCents`, and `settlingInHomeCents`. The money figures are minor units of the org home currency, each conversion valued at the stored reference rate on its own date. Rows whose sale currency has no usable rate are held out of the figure rather than counted at face value and reported per currency, in native minor units, in the additive maps `pendingUnconvertibleByCurrency`, `availableUnconvertibleByCurrency`, and `settlingUnconvertibleByCurrency` (each `null` when everything converted); those maps are the authoritative record of what was held. A campaign with no activity yet still gets an entry with zeros. ### `POST /api/v1/programs` **Scope:** `read_write`. **Role required:** `Manager` or higher (on JWT; API keys are Owner-equivalent and pass). Request: ```json { "name": "Holiday promo program", "slug": "holiday-2026", "description": "Limited-time 30% commission for Q4.", "termsUrl": "https://yourbrand.com/affiliate-terms", "defaultCommissionType": "Percentage", "defaultCommissionValue": 30.0, "defaultCommissionModel": "Cps", "cookieWindowDays": 30, "recurringMonths": null, "recurringDelayInvoices": 0, "attributionModel": "LastTouch", "autoApprove": false, "budgetCents": 500000, "referralDestinationUrl": "https://yourbrand.com/landing", "minPayoutCents": 2500 } ``` Response: `201 Created` with the new `ProgramResponse`. Fires no webhook (programs are not webhook-bearing today). Enums: - `defaultCommissionType`: `Percentage` | `Fixed` - `defaultCommissionModel`: `Cps` (per sale, flat fee or recurring %) | `Cpc` (per click) | `Cpl` (per lead). Names the campaign's **main payout leg**; `defaultCommissionType` / `defaultCommissionValue` carry its rate and partner-specific rates override it. Add the other legs with `leadBountyCents`, `clickRewardCents` and `saleCommissionType` / `saleCommissionValue` (any combination; see below). Legacy values still accepted for existing campaigns but not offered for new ones: `Cpa`, `RevShare`, `Hybrid` (all behave like `Cps`). - `attributionModel`: `FirstTouch` | `LastTouch` - `networkVisibility`: `Private` | `Networked` | `InviteOnly`. `Networked` lists the campaign in the Rekomi creator network after review and needs an active paid plan and a funding source on the organization; when either is missing, a request that turns listing on is saved as `Private` and returned that way with no error, so read `networkVisibility` back from the response. A campaign that is already `Networked` keeps that value; creators stop seeing it while the plan or the funding source is missing, and it shows again on its own once both are back. - `recurringMonths`: per-sale recurring duration. `null` / omitted = lifetime (every renewal); `0` = one-time (no renewals); `N` = every payment in each customer's first N calendar months, counted from their first payment (a monthly plan pays N times, a yearly plan pays once when N is 12); the window is per customer, keyed on the billing platform's customer id, so a resubscribe under a new subscription id stays inside it. On `PATCH`, send `-1` to clear an existing cap back to lifetime. - `budgetCents`: **campaigns that pay per click or per lead** (any primary). Cap on click + lead reward spend in minor units of the org home currency; once non-refunded click and lead rewards reach it, new clicks/leads stop earning. Sale commissions never count against it. Omit for uncapped, which is **not recommended** when paying per click or per lead. The cap can be at most USD 100,000,000 equivalent, valued against your home currency at today's stored reference rate; a larger value is rejected with `400 budget_out_of_range` (the body names `homeCurrency`), never clamped. Check spend with `GET /api/v1/programs/{id}/budget-status`. - `referralDestinationUrl`: the website affiliates promote (every campaign); a validated `rekomi.link` click redirects there with the affiliate's code. `minPayoutCents`: per-campaign minimum payout threshold (minor units). - `hostedCheckoutDoorEnabled` (bool, default false), `hostedCheckoutDoorMode` (`redirect` | `email`, default `redirect`), `hostedCheckoutThankYouUrl` (http(s) URL or `""` to clear): the checkout door for platforms where no script can run (beehiiv paid subscriptions). When enabled, share links are `https://rekomi.link/{code}?p={campaignId}` (an optional `&to=/path` deep-links inside the campaign site) and the platform's post-purchase redirect should be `https://rekomi.link/thanks?p={campaignId}&session_id={CHECKOUT_SESSION_ID}`. Sales credited this way carry `attributionMethod: "checkout_door"`. Invalid mode: `400 invalid_hosted_checkout_door_mode`; invalid URL: `400 invalid_hosted_checkout_thank_you_url`. - `leadBountyCents`: **any primary except `Cpl`** (a `Cpl` campaign's per-lead rate is `defaultCommissionValue`). The per-lead leg: a flat amount in minor units, paid for each qualified lead captured through the campaign's Rekomi-hosted lead form, on top of whatever else the campaign pays. Needs a Starter plan or above. The value is minor units of the org home currency (cents for a USD home, whole yen for a JPY home) and must sit between USD 0.10 and USD 1,000 equivalent, valued at today's stored reference rate for that currency; anything outside that band is rejected with `400 lead_bounty_out_of_range` (the body names `homeCurrency`), never clamped. Positive sets it, `0` clears it, omit = unchanged on `PATCH`. Ignored (nulled) on a `Cpl` primary. - `clickRewardCents`: **any primary except `Cpc`** (a `Cpc` campaign's per-click rate is `defaultCommissionValue`). The per-click leg: a flat amount in minor units of the org home currency, paid for each validated click (fraud-scored, de-duplicated, capped, under `budgetCents`), USD 0.01 to USD 1,000 equivalent at today's stored rate, else `400 click_reward_out_of_range`, never clamped. Positive sets it, `0` clears it, omit = unchanged on `PATCH`; ignored (nulled) on a `Cpc` primary. Turning it on makes the campaign **invite-only** (auto-approve, network listing and public discoverability are forced off) and needs a Starter plan or above. - `saleCommissionType` / `saleCommissionValue`: **`Cpc` / `Cpl` primaries only.** The per-sale leg on a click or lead campaign: `Percentage` (0 to 100) or `Fixed` (major units of the currency each sale arrives in, up to 1,000,000), sent together. Renewals follow `recurringMonths` / `recurringDelayInvoices` exactly like a sale campaign. On `PATCH`, `saleCommissionValue: 0` clears the pair. Nulled on sale primaries (their sale rate is the default rate). Partner-specific rates never apply to it (they override the primary per-unit rate). - `defaultCommissionValue` bounds: `Percentage` is 0 to 100. A `Fixed` value on `Cpc`/`Cpl` is the per-click or per-lead fee in major units of the org home currency and can be at most USD 1,000 equivalent (`400 unit_fee_out_of_range`); a `Fixed` value on a sale model pays that many units of each sale's own currency and can be at most USD 1,000,000 equivalent (`400 fixed_commission_out_of_range`). Both ceilings are valued at today's stored reference rate and rejected rather than clamped. ### `PATCH /api/v1/programs/{id}` **Scope:** `read_write`. **Role:** `Manager`. All fields optional; only the keys you send are updated. ```json { "name": "Holiday promo (extended)", "isActive": true, "defaultCommissionValue": 35.0, "autoApprove": true, "customDomain": "go.yourbrand.com", "networkVisibility": "Networked" } ``` Response: 200 with the updated `ProgramResponse`. `customDomainVerified` resets to `false` if `customDomain` changes; verify via the dashboard. ### `DELETE /api/v1/programs/{id}` **Scope:** `read_write`. **Role:** `Owner`. Soft-deletes (sets `archivedAt`). Returns `204 No Content`. ### `GET /api/v1/programs/{id}/tracking-status` **Scope:** `read`. Install verification: whether the campaign has ever recorded a click or conversion. Returns `{ verified, lastClickAt, lastConversionAt, totalClicks30d, totalConversions30d }`. ### `GET /api/v1/programs/{id}/recent-events` **Scope:** `read`. Most recent clicks and conversions for the campaign, interleaved. Query: `limit` (default 20, max 100). ### `GET /api/v1/programs/check-slug` **Scope:** `read`. Pre-flight slug availability before you create a campaign. Query: `slug`. Returns `{ "available": true, "slug": "spring-launch" }`, or `{ "available": false, "slug": "spring-launch", "suggestion": "spring-launch-2" }` with the first free `-2` to `-10` variant. `400 slug_required` or `400 invalid_slug_format` (4 to 64 lowercase letters, digits and hyphens, starting and ending alphanumeric). ### `PATCH /api/v1/programs/{id}/slug` **Scope:** `read_write`. **Role:** `Manager`. Change a campaign's slug, the path segment of its signup page. The previous slug is kept as a redirect alias, so old links 308-redirect to the new one. Body: `{ "slug": "new-slug" }`. Returns `{ "slug": "new-slug", "previousSlug": "old-slug" }`. Errors: `400 invalid_slug_format`, `400 slug_reserved`, `409` when the slug is taken by another live campaign or is a retired alias of a different campaign. ### `GET /api/v1/programs/{id}/tracking-status/lookup` **Scope:** `read`. What Rekomi saw for one buyer, for support on a hosted checkout. Query: `email`. Plus-addressed variants of one inbox count as the same person. Returns the lead (if any), the attributed sale (if any), the last purchases the connected gateway delivered for that email, and a one-word `verdict`: `attributed`, `attributed_by_platform`, `lead_and_purchase_but_no_sale`, `lead_only`, `purchase_without_lead` or `nothing_seen`. ### `POST /api/v1/programs/{id}/custom-domain/verify` **Scope:** `read_write`. **Role:** `Admin`. **Plan tier:** any paid plan, trials included. Re-runs DNS verification for the campaign's custom tracking domain and provisions the certificate on success. Returns `{ "verified": true, "domain": "go.yourbrand.com", "resolvedTarget": "cname.rekomi.com", "errorReason": null }`. Rate-limited. ### `POST /api/v1/programs/{id}/test-event` **Scope:** `read_write`. **Role:** `Manager`. Fires a synthetic click plus a $10.00 conversion for the campaign's seeded test affiliate through the real pipeline, including the `conversion.created` webhook, so you can test an integration end to end. Test rows show in the conversions list and campaign activity but never count toward payouts or dashboard totals. Returns `{ "clickId": "...", "conversionId": "...", "commissionCents": 100 }`. ## Affiliates **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. ### `GET /api/v1/affiliates` **Scope:** `read`. List affiliates. Cursor-paginated. Query: `take` (default 50, max 200), `skip`, `cursor`, `programId`. There is no status or text-search filter on this list; pending applications live at `GET /api/v1/affiliates/applications`. ```json { "items": [ { "id": "01234567-...", "programId": "0789abcd-...", "email": "jane@creator.com", "fullName": "Jane Doe", "status": "Approved", "customCommissionType": null, "customCommissionValue": null, "tags": ["youtube", "tier-1"], "notes": "Top performer Q1 2026", "source": "Public", "appliedAt": "2026-04-15T10:00:00Z", "approvedAt": "2026-04-16T11:30:00Z", "createdAt": "2026-04-15T10:00:00Z", "links": [ { "id": "abcdef12-...", "affiliateId": "01234567-...", "slug": "jane-recommends", "customAlias": null, "isDefault": true, "format": "Short", "createdAt": "2026-04-16T11:30:00Z" } ] } ], "nextCursor": null, "hasMore": false } ``` Notes: - `email` is the affiliate's email, visible to you as the brand owner (your own data). - `notes` is brand-side internal commentary. Do not re-share with the affiliate. - The response intentionally **omits** `ClerkUserId`, `StripeConnectAccountId`, `PaypalEmail`, `TaxFormStatus`, `KycStatus`. For API-key callers, the response also omits `applicationData` (the form-builder submission JSON). The dashboard JWT path keeps `applicationData` so the in-product review screen can render it. - `status` enum: `Pending` | `Approved` | `Rejected` | `Paused` | `Banned`. - `source` enum: `Direct` | `Public` | `Invite` | `Network`. - `format` enum (on each `links[]` entry): `Short` | `Query` | `Path` | `Hash`. - The default link (`isDefault: true`) is the affiliate's main account slug. On sale-model campaigns, affiliates can hold up to 20 links per campaign (including their main link), managed from their dashboard. ### `GET /api/v1/affiliates/stats` **Scope:** `read`. One row per affiliate with the numbers the dashboard's affiliate reports show: clicks (`visitors`), leads, sales (`conversions`), commission buckets and revenue, all money in minor units normalized to your home currency. Lifetime figures by default. Query: `programId` (optional: one campaign), `since` and `until` (optional ISO timestamps; `since` inclusive, `until` exclusive). ```json { "currency": "USD", "items": [ { "affiliateId": "01234567-...", "visitors": 43, "leads": 2, "conversions": 5, "unpaidCommissionCents": 12000, "dueNowCommissionCents": 8000, "paidCommissionCents": 45000, "processingCommissionCents": 0, "failedPayoutCommissionCents": 0, "grossRevenueCents": 310000, "netRevenueCents": 298000 } ] } ``` For one affiliate's day-by-day shape use `GET /api/v1/dashboard/timeseries` with `affiliateId`. ### `GET /api/v1/affiliates/{id}` **Scope:** `read`. Single `AffiliateResponse`. Returns 404 if the affiliate isn't in your org. ### `GET /api/v1/affiliates/applications` **Scope:** `read`. List pending applications awaiting approval. Same per-affiliate shape as the main list, filtered to `status=Pending`. Returns a **flat array** paged with `take`/`skip` (no cursor envelope). ### `GET /api/v1/affiliates/{id}/tax-form` **Scope:** `read`. **Role:** `Admin`. Returns a 5-minute signed URL to download the affiliate's W-9 / W-8BEN PDF. Audit-logged on every read. ```json { "id": "tx-form-uuid", "affiliateId": "01234567-...", "type": "W9", "submittedAt": "2026-04-20T14:00:00Z", "signedUrl": "https://nyc3.digitaloceanspaces.com/rekomi-tax/...sig=...&exp=...", "expiresInSeconds": 300 } ``` The `signedUrl` is a time-limited (5 minutes) presigned URL; it is not single-use, so do not log or share it, and do not cache it. When the form is held by the payout provider rather than stored by Rekomi, the endpoint returns `{ "heldByStripe": true }` instead of the shape above. ### `POST /api/v1/affiliates` **Scope:** `read_write`. Admin-path affiliate creation (skips the application form). The new affiliate's default tracking link is auto-generated. Request: ```json { "programId": "0789abcd-...", "email": "newaffiliate@example.com", "fullName": "New Affiliate", "status": "Approved", "customCommissionType": null, "customCommissionValue": null, "customAlias": null } ``` Response: 201 with the new `AffiliateResponse`. Fires `affiliate.created`. If `status=Approved`, also fires `affiliate.approved`. ### `PATCH /api/v1/affiliates/{id}` **Scope:** `read_write`. Update affiliate fields. All optional. ```json { "fullName": "Jane Doe (updated)", "status": "Approved", "customCommissionType": "Percentage", "customCommissionValue": 25.0, "notes": "Promoted to tier-1 on 2026-05-12.", "tags": ["youtube", "tier-1", "vip"] } ``` Response: 200 with the updated `AffiliateResponse`. ### `POST /api/v1/affiliates/{id}/approve` **Scope:** `read_write`. Approves a pending application. Fires `affiliate.approved`. Request body: `{}` (empty). Response: 200 with the updated `AffiliateResponse`. ### `POST /api/v1/affiliates/{id}/reject` **Scope:** `read_write`. Rejects a pending application. ```json { "reason": "Off-topic audience" } ``` Response: 200 with the updated `AffiliateResponse` (status now `Rejected`). ### `POST /api/v1/affiliates/bulk-action` **Scope:** `read_write`. **Role:** `Admin` (JWT) with a verified email; API-key callers pass the role gate by design. Bulk `approve`, `pause`, or `delete` up to **200** affiliates in one call. Idempotent (re-approving an approved affiliate is a no-op), audit-logged per action. ```json { "affiliateIds": ["uuid-1", "uuid-2"], "action": "approve" } ``` ### `POST /api/affiliates/bulk/approve` and `POST /api/affiliates/bulk/pause` Bulk approve or pause affiliates. These are **dashboard-session endpoints** (Clerk JWT only; there is no API-key bearer alias). **Role:** `Manager` with a verified email. Up to **500** ids per call. ```json { "ids": ["uuid-1", "uuid-2", "..."] } ``` Response reports how many rows were affected; ids not in your org or not matching the action's precondition are skipped. ### `GET /api/v1/affiliates/export.csv` **Scope:** `read`. The full roster as CSV, one row per affiliate (org-wide, or one campaign with `programId`), with the identity columns the dashboard shows plus the stats columns of `GET /api/v1/affiliates/stats` (`clicks`, `leads`, `sales`, the commission buckets, gross and net revenue, `currency`). Optional `since` and `until` window the stats columns. Streams up to 50,000 rows. Rate-limited like `/stats`. ### `GET /api/v1/affiliates/{id}/commission-summary` **Scope:** `read`. One affiliate's money, bucketed per currency, using the same rules a payout run uses, so `dueNowCents` is exactly what the next run would pay. ```json { "primaryCurrency": "USD", "graceDays": 30, "hasStripeConnect": true, "payoutReady": true, "rail": "StripeConnect", "blockedReason": null, "byCurrency": [ { "currency": "USD", "dueNowCents": 8000, "pendingInGraceCents": 4000, "paidCents": 45000, "processingCents": 0, "refundedCents": 1200, "totalCommissionCents": 58200, "grossRevenueCents": 310000, "netRevenueCents": 298000, "dueConversionCount": 3 } ], "referrals": { "visitors": 43, "leads": 2, "conversions": 5 } } ``` ### `GET /api/v1/affiliates/{id}/commissions` **Scope:** `read`. Paginated commission rows for one affiliate and one bucket. Query: `bucket` (`due` | `pending` | `paid` | `refunded`), `currency` (optional), `page` (default 1), `pageSize` (default 25). Only payable rows are listed; a $0 lead or click marker never appears. Customer identity is included because this is the brand side. ```json { "items": [ { "conversionId": "c0nv0001-...", "programId": "0789abcd-...", "programName": "Spring launch", "amountCents": 9900, "commissionCents": 1980, "netCommissionCents": 1980, "refundedAmountCents": 0, "currency": "USD", "type": "Sale", "payoutStatus": "Paid", "payoutId": "payout-uuid", "customerName": "Sam Lee", "customerEmail": "sam@example.com", "customerAvatarUrl": null, "createdAt": "2026-04-16T11:30:00Z" } ], "page": 1, "pageSize": 25, "totalCount": 12, "currency": "USD", "counts": { "due": 3, "pending": 2, "paid": 6, "refunded": 1 } } ``` ### `GET /api/v1/affiliates/{id}/crm-sync` **Scope:** `read`. One row per connected CRM integration with the latest sync job for this affiliate. ```json [ { "integrationId": "...", "slug": "klaviyo", "displayName": "Klaviyo", "integrationEnabled": true, "defaultListName": "Voltra Affiliates", "jobId": "...", "status": "Succeeded", "attempts": 1, "lastAttemptAt": "2026-09-12T12:19:00Z", "succeededAt": "2026-09-12T12:19:02Z", "error": null } ] ``` `status` is `NeverSynced` when no job exists yet, otherwise the job state (`Pending`, `Succeeded`, `Failed`, `Skipped`); `error` carries the failure or skip reason. ### `POST /api/v1/affiliates/{id}/crm-resync` **Scope:** `read_write`. **Plan tier:** paid plans only (trials get `402`). Drops the affiliate's prior sync jobs and re-enqueues a fresh sync to every connected CRM. Returns `{ "deletedPriorJobs": 2 }`. Rate-limited. ### `POST /api/v1/affiliates/{id}/remind-payout-setup` **Scope:** `read_write`. **Role:** `Admin`. Emails an affiliate who has earned commission but has no payout account yet, asking them to connect one. Once per affiliate per 24 hours. Returns `{ "sent": true, "email": "jane@creator.com" }`; `409 already_connected` when they already have one; `422 no_email`; `429` inside the cooldown. ### `GET /api/v1/affiliates/{id}/claim-reminder/context` **Scope:** `read`. For imported affiliates who have not claimed their Rekomi account yet. Returns what a reminder email can merge in: the person's campaigns, accrued earnings per currency, referral count, the resolved `{{claim.campaigns}}` and `{{claim.earnings}}` sample values, and the cooldown state. ```json { "email": "jane@creator.com", "firstName": "Jane", "campaigns": [ { "programId": "0789abcd-...", "name": "Spring launch" } ], "earnings": [ { "currency": "USD", "totalCommissionCents": 12000 } ], "referralCount": 7, "resolvedTokens": { "campaigns": "Spring launch", "earnings": "$120.00" }, "cooldown": { "canSend": true, "retryAfterHours": 0, "sendsRemaining": 3 } } ``` `409 already_joined` when the person already has an account, `409 not_eligible` for pending, rejected or banned rows. ### `POST /api/v1/affiliates/{id}/claim-reminder` **Scope:** `read_write`. Sends the claim reminder to one person. Body: `{ "subject": "...", "bodyHtml": "..." }`. The body is sanitized server-side, merge tokens resolve at send time and the claim link is appended by Rekomi. Returns `{ "sent": true, "email": "..." }`. Errors: `409 already_joined` or `not_eligible`, `422 unsafe_subject`, `empty_body` or `no_email`, `429 cooldown` (24 hours per person, with `Retry-After`) or `resend_limit_reached`. Rate-limited. ### `POST /api/v1/affiliates/claim-reminder/bulk` **Scope:** `read_write`. **Plan tier:** paid plans only. Body: `{ "ids": ["..."], "subject": "...", "bodyHtml": "..." }`, up to 200 ids. Dedupes by email, skips people who already joined or are on cooldown, and queues one send per person at about one per second. Returns aggregate counts only: `{ "queued": 18, "skippedJoined": 1, "skippedCooldown": 0, "skippedIneligible": 1 }`. ## Conversions **Plan tier:** Starter+ for API-key callers (read + write programmatic access). S2S conversion creation has its own gate at Starter+. JWT dashboard is unrestricted. These endpoints are the commission tracking behind every affiliate's ledger: they're how a conversion becomes a payable commission. ### `GET /api/v1/conversions` **Scope:** `read`. List conversions. Cursor-paginated. Query: `take` (max 200; note this list returns **100** rows when `take` is omitted, unlike the 50-row default on other lists), `cursor`, `affiliateId`, `programId`, `status` (`Pending` | `Approved` | `Paid` | `Denied` | `Refunded` | `Invalidated`), `kind` (`sale` | `lead` | `click`; the same split as the Sales, Leads and Clicks tabs on the Conversions page), `since` (ISO). Use `status=Invalidated` to pull the click/lead (CPC/CPL) fraud-quarantine review queue (traffic held out of payment as invalid). ```json { "items": [ { "id": "c0nv0001-...", "affiliateId": "01234567-...", "programId": "0789abcd-...", "amountCents": 9900, "currency": "USD", "commissionCents": 1980, "status": "Approved", "type": "Recurring", "stripeSubscriptionId": "sub_1ABC...", "stripeCustomerId": "cus_1ABC...", "externalEventId": "in_1XYZ...", "createdAt": "2026-05-10T16:00:00Z", "refundedAt": null, "attributionMethod": "coupon", "couponCode": "SARAH20", "subId": null } ], "nextCursor": null, "hasMore": false } ``` Notes: - `type` enum: `OneTime` | `Recurring` | `Bonus` | `ClickReward` | `LeadReward`. - `status` enum: `Pending` | `Approved` | `Paid` | `Refunded` | `Denied` | `Invalidated`. `Invalidated` = fraud-quarantined click/lead traffic held out of payment; validate it (release) or invalidate a Pending row from the Conversions page or the API (see below). - `attributionMethod` is how the sale was attributed (for example `coupon`, `metadata_or_customer`, `cpc_aggregation`, `sub_affiliate_override`, or `null` for S2S). The full value set and how the dashboard groups them is in the [webhooks reference](/docs/developers/webhooks). `couponCode` is the redeemed code when (and only when) the sale was attributed by a coupon, otherwise `null`. - **Lead rows** use `type` = `LeadReward` with `amountCents` of `0`. A free identified signup (attribution `lead_signup`) also has `commissionCents` of `0` and ties a customer email to the referral before any payment; a lead on a per-lead (CPL) campaign (attribution `lead_form`) carries the campaign's fixed per-lead fee in `commissionCents`. Neither is a sale; a later paid sale is recorded as a separate `OneTime` / `Recurring` row. The reliable zero-commission discriminator is the `lead_signup` attribution, not the `LeadReward` type. See [Track leads and signups](/docs/developers/lead-tracking). - `payability` says whether a row carries money: `payable` (a sale, or a lead or click the campaign pays for; it follows the commission lifecycle above), `not_paid_on_campaign` (a lead or click recorded on a campaign that does not pay for that type; kept for attribution and fraud review, never a commission) or `attribution_only` (a $0 lead or click marker on a campaign that does pay for that leg; the paid lead or click is a separate row). The list and detail responses both carry it. - `stripeSubscriptionId` and `stripeCustomerId` are the brand's own Stripe identifiers (i.e., your `cus_*` / `sub_*`). They are not the affiliate's Stripe identifiers (affiliates don't have one). Use them to join Rekomi conversions back to your Stripe data. - `subId` is the channel tag the customer's visit carried (`?sub=yt` on the affiliate's link), or `null` when untagged. Filter the list with `subId=yt`, or `subId=untagged` for rows with none. See [Sub-IDs](/docs/developers/pixel-tracking#sub-ids). - For a deep-dive on a single conversion including subscription event history, hit `GET /api/v1/conversions/{id}`. ### `GET /api/v1/conversions/{id}` **Scope:** `read`. Includes subscription event timeline. ```json { "id": "c0nv0001-...", "affiliateId": "01234567-...", "programId": "0789abcd-...", "amountCents": 9900, "currency": "USD", "commissionCents": 1980, "status": "Approved", "type": "Recurring", "stripeSubscriptionId": "sub_1ABC...", "stripeCustomerId": "cus_1ABC...", "externalEventId": "in_1XYZ...", "createdAt": "2026-05-10T16:00:00Z", "refundedAt": null, "attributionMethod": "coupon", "couponCode": "SARAH20", "events": [ { "id": "evt-uuid-1", "eventType": "initial", "amountCents": 9900, "commissionCents": 1980, "occurredAt": "2026-05-10T16:00:00Z" }, { "id": "evt-uuid-2", "eventType": "renewal", "amountCents": 9900, "commissionCents": 1980, "occurredAt": "2026-06-10T16:00:00Z" } ] } ``` ### `GET /api/v1/conversions/export.csv` **Scope:** `read`. CSV export. Same filters as the list endpoint plus `until`. Capped at 50,000 rows per call. Audit-logged. Header row (verified against code): ```csv id,affiliate_id,program_id,amount_cents,currency,commission_cents,status,type,external_event_id,created_at,refunded_at,attribution_method,coupon_code,sub_id ``` `attribution_method`, `coupon_code` and `sub_id` are the last three columns (appended, so existing column positions are unchanged). `coupon_code` is populated only for coupon-attributed rows; `sub_id` is the channel tag, empty when untagged. Stripe IDs are intentionally omitted from CSV; use the JSON endpoint if you need them. ### `POST /api/v1/conversions` **Scope:** `read_write`. **Role required:** `Manager` or higher with a verified email (on JWT; API keys are Owner-equivalent and pass). Rate-limited under the strict-write policy. Manually record a sale that happened outside your connected billing, or mint a standalone bonus commission for one affiliate. The row runs through the same pipeline as every other conversion (attribution method `manual`), so the campaign's commission engine, sub-affiliate overrides, the `conversion.created` webhook, and the commission-earned notification all apply. The commission becomes payable through the normal grace and threshold flow. This creates payable money; it is audit-logged as `conversion.manual_created`. Request: ```json { "programId": "0789abcd-...", "affiliateId": "01234567-...", "kind": "sale", "amountCents": 9900, "currency": "USD", "commissionCents": null, "customerEmail": "buyer@example.com", "occurredAt": "2026-05-01T12:00:00Z", "note": "Invoice #1042, paid by bank transfer" } ``` - `kind`: `sale` (default) | `bonus`. - `amountCents`: **sale only**, required and > 0, in minor units of `currency`. Upper bound 1,000,000,000,000 minor units, the same currency-blind sanity bound every ingest path uses (beyond any plausible single sale). A `bonus` must omit it (`bonus_has_no_amount`). - `currency`: optional; defaults to your org home currency. Must be on the supported allowlist (`unsupported_currency`). - `commissionCents`: for a `sale`, an optional override of the engine-computed commission, capped at **5x `amountCents`** (`commission_exceeds_cap`). For a `bonus`, required and > 0, in minor units of `currency`, with the same 1,000,000,000,000 minor-unit upper bound. - `customerEmail`: optional; ties the sale to a customer. - `occurredAt`: optional backdate (ISO 8601). Never in the future, at most 5 years back. A backdated sale that is already past the refund-hold window becomes payable immediately. - `note`: optional, trimmed to 1,000 characters; echoed back in the response envelope. The affiliate must belong to the selected program and be `Approved` (`affiliate_not_in_program`, 409 `affiliate_not_approved`). The seeded Test Affiliate is refused (`test_affiliate_not_allowed`); use the one-click test event instead. A suspended campaign returns 409 `program_suspended`. There is no built-in dedup on this endpoint (each call mints a fresh internal event id), so a blind retry creates a second conversion; send an `Idempotency-Key` header on any request you might retry. Response: `201 Created`. ```json { "conversion": { "id": "c0nv0002-...", "affiliateId": "01234567-...", "programId": "0789abcd-...", "amountCents": 9900, "currency": "USD", "commissionCents": 1980, "status": "Pending", "type": "OneTime", "attributionMethod": "manual", "createdAt": "2026-05-01T12:00:00Z" }, "note": "Invoice #1042, paid by bank transfer" } ``` A `bonus` comes back with `amountCents: 0` and `type: "Bonus"`. ### `GET /api/v1/conversions/kind-counts` **Scope:** `read`. All-time row counts per kind for the organization, invalidated rows excluded: `{ "sales": 120, "leads": 34, "clicks": 0 }`. The dashboard uses it to decide whether the Leads and Clicks tabs render at all. ## Click and lead fraud review and budget (CPC/CPL) Campaigns that pay per click or per lead (CPC/CPL), including the paid lead endpoint, fraud review queue, and budget status, are in **limited preview**. The paid lead endpoint returns `404` until the preview is enabled for your account. These endpoints operate the click and lead fraud workflow and spend caps. The write actions require the **Manager** role. ### `POST /api/v1/conversions/{id}/validate` **Role:** Manager. Returns a fraud-quarantined (`Invalidated`) conversion to `Pending` so it re-enters the normal approval flow, i.e. clears a false positive. Fires `conversion.validated`. ### `POST /api/v1/conversions/{id}/invalidate` **Role:** Manager. Quarantines a `Pending` or `Approved` conversion you judge fraudulent, setting it `Invalidated` so it is not paid. Fires `conversion.invalidated`. ### `GET /api/v1/programs/{id}/budget-status` **Scope:** `read`. The campaign's click + lead spend-cap status: total budget, spent (non-refunded commission), and remaining, in minor units of the org home currency. Unlimited when the campaign has no budget set. Spend that could not be valued in the home currency (a sale currency with no usable exchange rate for the row's date) is held out of `spentCents` rather than counted at face value, and reported in `fxUnconvertibleByCurrency`, a map of currency code to native minor units; that map is the authoritative figure. The older scalar `fxUnconvertibleCents` stays on the wire for compatibility, but it is a sum of native minor units across currencies, a presence signal rather than a money amount: when it is non-zero, read the map. ### `GET /api/v1/fraud-events` **Scope:** `read`. Lists fraud signals Rekomi flagged on paid clicks and leads (high-risk IPs, bots, self-leads, disposable emails, per-IP-cap hits, abusive affiliates). These were already dropped, with no payout. Query: `programId`, `status` (`open` default; also `resolved`, `dismissed`, or `all`). - `PATCH /api/v1/fraud-events/{id}` (Manager) with `{ "action": "resolve" | "dismiss" }` reviews a signal. - `POST /api/v1/fraud-events/ban-affiliate/{affiliateId}` (Manager) bans a partner: stops all their future clicks, leads, and conversions from earning. ### `GET /api/v1/fraud-events/summary` **Scope:** `read`. **Role:** Manager. Aggregate fraud stats for one campaign that pays per click or per lead. Query: `programId` (required; 400 `program_id_required` without it), `days` (default 30, clamped 1-365). Returns `{ windowDays, blockedClicks, blockedClicksByReason, heldClicks, quarantinedLeads, estimatedNotBilledCents, currency, fraudStrictness }`. ### `GET /api/v1/uncredited-conversions` **Scope:** `read`. Sales that arrived attributed to an affiliate who could not earn at that moment (not `Approved` at sale time: pending, paused, rejected, or banned). Query: `status` (`open` default | `dismissed` | `all`), `affiliateId`. Returns up to 500 rows of `{ id, affiliateId, affiliateEmail, affiliateName, programId, amountCents, currency, reason, affiliateStatusAtDrop, status, occurredAt }`. Companion endpoints: - `GET /api/v1/uncredited-conversions/summary` returns `{ openCount, byCurrency: [{ currency, amountCents, count }] }`. - `PATCH /api/v1/uncredited-conversions/{id}/dismiss` (Manager) marks a row reviewed. If a row was a mistake (an affiliate paused by accident, say), fix the affiliate's status and credit them with a [manual conversion](#post-apiv1conversions). ### `POST /api/v1/lead` (paid lead on a campaign that pays per lead, CPL) **Limited preview** (returns `404` until enabled for your account). Records a **billable** lead on a campaign that pays per lead. Body: `{ slug, programId?, email, name?, token }`, where `token` is a **required Cloudflare Turnstile** token. The lead passes email-format, disposable-domain, self-lead, per-campaign dedup, daily-cap, budget, and IPQS fraud checks. A high-risk lead is quarantined (`Invalidated`, opaque `200`); a budget-full campaign returns `409 campaign_budget_reached`; if fraud scoring is unreachable the call returns `503` (retry) and records nothing. This is distinct from the zero-commission signup lead (`/api/v1/lead/signup`); see [Track leads and signups](/docs/developers/lead-tracking). ## Import (bulk migration) **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. **Scope:** `read_write`. Generic, platform-agnostic bulk import. Unlike the dashboard migrate flow (which parses a specific vendor CSV), you post **canonical JSON rows** here, so any source works once you map it into the Rekomi shape. Both surfaces converge on the same audited services: there is exactly one path that creates affiliates and one that records conversions. Shared safeguards for every endpoint below: - **Row cap:** at most **1,000 rows per request** (`row_cap_exceeded`, 400). Page larger imports. - **Body size:** ~4 MB JSON (`413` over the limit). - **Rate limit:** the dedicated `import` policy, **6 requests/min** per caller (`429` over the limit). - **Concurrency:** one import per org at a time. A second concurrent import returns `409 migration_already_in_progress` (shared with the dashboard importer). - **Tenant isolation:** a `programId` that is not in your organization returns `404 program_not_found`. - **Revertible:** each import persists a `MigrationJob`; the returned `jobId` appears in import history and can be undone from the dashboard. ### `POST /api/v1/affiliates/import` **Scope:** `read_write`. Bulk-import an affiliate roster. Dedupes by email within the campaign AND within the batch (re-running is idempotent: existing emails come back as `skipped`, never duplicated). Each row gets a preserved or freshly-generated tracking slug. Request: ```json { "programId": "0789abcd-...", "affiliates": [ { "email": "alex@example.com", "fullName": "Alex Smith", "status": "approved", "paypalEmail": "alex@example.com", "slug": "alex" }, { "email": "jordan@example.com", "fullName": "Jordan Lee" } ], "includeStatuses": ["approved", "pending"], "notifyAffiliates": false, "notificationMessageHtml": null } ``` - `email` is required per row. `status` is optional and **defaults to `approved` when omitted** (the affiliate was already live on the prior platform); an unrecognized value falls back to `pending`. Accepted: `approved` | `pending` | `rejected` | `paused` | `banned`. - `slug` (optional) preserves the affiliate's existing referral slug so their old links keep working; if it is taken or invalid, a fresh slug is generated. - `includeStatuses` (optional) is an allowlist; rows whose mapped status is not in the set are skipped (`status_excluded`). Omit to import every row. - `notifyAffiliates` requires **Growth+** with an active subscription; on a lower tier the import still succeeds but no emails are sent. Response: 200. ```json { "jobId": "1234abcd-...", "status": "Completed", "kind": "Affiliates", "total": 2, "imported": 2, "skipped": 0, "errored": 0, "reasons": {} } ``` `reasons` tallies why rows did not import (e.g. `already_exists`, `status_excluded`, `missing_email`). ### `POST /api/v1/conversions/import/preview` **Scope:** `read_write`. **Dry-run** of a conversion import: classifies and resolves affiliates and tallies money but **writes nothing**. Always call this before the real import so you can show the unpaid balance before authorizing payment. Same row shape and validation as the import endpoint. Request: `{ "programId": "...", "conversions": [ ... ], "excludePaid": false }` (same row shape as the import). Pass the same `excludePaid` you intend to use on the import: when `true`, paid rows are reported as skipped (`excluded_paid`) and excluded from `wouldImport` / `paidBalance`, so the preview matches exactly what the import will write. Response: 200. ```json { "rowsTotal": 2, "wouldImport": 2, "wouldSkip": 0, "wouldError": 0, "skipReasons": {}, "paidBalance": [{ "currency": "USD", "commissionCents": 2500, "count": 1 }], "unpaidBalance": [{ "currency": "USD", "commissionCents": 5000, "count": 1 }] } ``` `unpaidBalance` is exactly how much commission per currency would become **payable** if you then import with `confirmUnpaidPayout=true`. ### `POST /api/v1/conversions/import` **Scope:** `read_write`. Import historical conversions/commissions so migrated affiliates keep their earnings and stats (and, with Stripe IDs, future renewals re-credit them). Idempotent (re-running skips already-imported rows; never double-pays). Request: ```json { "programId": "0789abcd-...", "conversions": [ { "affiliateEmail": "alex@example.com", "affiliateSlug": null, "customerEmail": "buyer@example.com", "customerName": "Buyer One", "amountCents": 10000, "commissionCents": 2500, "currency": "USD", "occurredAt": "2025-01-15T00:00:00Z", "paid": true, "type": "OneTime", "stripeCustomerId": "cus_ABC", "stripeSubscriptionId": "sub_XYZ", "externalId": "src-row-1" } ], "confirmUnpaidPayout": false, "excludePaid": false } ``` Per-row validation (any failure returns `400 invalid_rows` with `{ index, error }` per offending row, before anything is written): - At least one of `affiliateEmail` / `affiliateSlug` is required (`affiliate_email_or_slug_required`). - `amountCents` and `commissionCents` must be integers in `[0, 1000000000000]` (minor units of `currency`; a currency-blind sanity bound beyond any plausible single sale). Negative or over-cap is rejected (`amount_out_of_range` / `commission_out_of_range`). - `currency` must be a supported ISO-4217 code (`unsupported_currency`). - `occurredAt` is required (ISO 8601; sets the historical cohort month). - `type` is `OneTime` (default) or `Recurring` (`invalid_type`). **Money safety (read this):** - `paid: true` rows are recorded as settled history (`Paid`, locked) and **can never be paid again**. - `paid: false` rows are recorded as **locked history by default**. They become a **payable** balance (`Approved`, an amount Rekomi will pay out) **only when you set `confirmUnpaidPayout: true`**. `confirmUnpaidPayout` defaults to `false`. - Importing never moves money by itself; payouts remain operator-run and Stripe-Connect-gated. - `excludePaid: true` skips already-paid rows entirely (import only open balances). Response: 200. ```json { "jobId": "1234abcd-...", "status": "Completed", "kind": "Conversions", "total": 1, "imported": 1, "skipped": 0, "errored": 0, "unpaidConfirmed": [], "unpaidConfirmedCurrency": null, "unpaidConfirmedCents": 0, "confirmUnpaidPayout": false, "excludePaid": false, "reasons": {} } ``` `unpaidConfirmed` is the exact commission this import made payable (the `Approved` conversions it created), listed per currency as `{ currency, commissionCents, count }` records in the same shape as the preview's `unpaidBalance`. A batch that confirmed JPY 1,000 and USD 10.00 returns `[{ "currency": "JPY", "commissionCents": 1000, "count": 1 }, { "currency": "USD", "commissionCents": 1000, "count": 1 }]`. It is empty whenever `confirmUnpaidPayout` is `false`. `unpaidConfirmedCurrency` is that currency's code when exactly one currency became payable, otherwise `null`. `unpaidConfirmedCents` is kept for older integrations. It is a raw sum of native minor units across every currency in the batch, so read it as a money amount only when `unpaidConfirmedCurrency` is set. When `unpaidConfirmedCurrency` is `null` it only signals that something became payable; the amounts are in `unpaidConfirmed`. ## Payouts **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. ### `GET /api/v1/payouts` **Scope:** `read`. List payouts, one row per affiliate per run. Cursor-paginated. ```json { "items": [ { "id": "payout-uuid", "affiliateId": "01234567-...", "amountCents": 9800, "currency": "USD", "method": "StripeConnect", "status": "Paid", "stripeTransferId": "tr_1ABC...", "externalTransactionId": null, "notes": null, "failureReason": null, "periodStart": "2026-04-01", "periodEnd": "2026-04-30", "createdAt": "2026-05-01T09:00:00Z", "paidAt": "2026-05-01T09:00:05Z", "failedAt": null, "lineItems": [ { "id": "li-uuid-1", "conversionId": "c0nv0001-...", "amountCents": 1980, "currency": "USD", "nativeAmountCents": 1980, "nativeCurrency": "USD", "fxRate": 1, "fxRateDate": "2026-05-01", "reversedAmountCents": 0, "reversedAt": null }, { "id": "li-uuid-2", "conversionId": "c0nv0002-...", "amountCents": 7820, "currency": "USD", "nativeAmountCents": 7200, "nativeCurrency": "EUR", "fxRate": 0.9207, "fxRateDate": "2026-04-30", "reversedAmountCents": 0, "reversedAt": null } ] } ], "nextCursor": null, "hasMore": false } ``` Enums: - `method` on stored payouts is `StripeConnect`, `PayPal`, or `GlobalPayouts` (`Manual` appears only on historical rows). Requests may send `StripeConnect` or `Auto` (pay each affiliate on their resolved rail); `Wise` is not supported. - `status`: `Pending` | `Processing` | `Paid` | `Failed`. Each payout row is one affiliate's payout from one run, and `lineItems` lists the commissions inside it: - `amountCents` + `currency`: what the line paid (USD on consolidated runs; a legacy single-currency run carries that run's currency). - `nativeAmountCents` + `nativeCurrency`: the commission as it was earned. - `fxRate`: the stored USD-base reference rate the line converted at, expressed as native major units per 1 USD (the exchange-rate value for `nativeCurrency` on `fxRateDate`; USD lines carry `1`). `usd = native / fxRate` reproduces the line, and the rate reads as "0.9207 EUR per USD", never as USD per native unit. - `fxRateDate`: the date the rate was published (the run date on USD lines). - `reversedAmountCents` + `reversedAt`: a clawback applied after payment (`0` / `null` while the line is intact). The four FX fields are `null` on payouts created before line-level FX was recorded. ### `GET /api/v1/payouts/{id}` **Scope:** `read`. Single `PayoutResponse` (same shape as a list item). Payout funding (linking a funding bank on any rail, enabling bank-transfer funding, running, retrying, or reversing payouts) is deliberately not exposed to API keys or OAuth tokens: money movement requires an interactive Owner or Admin dashboard session. The API surface is read plus preview only. See [Payouts: funding your payouts](/docs/brands/payouts#funding-your-payouts). `GET /api/v1/payouts/funding/status` is readable with an API key. It returns the funding mode (`debit` or `balance`), the bank-transfer balance in cents when the mode is `balance`, the verification state, `payoutsAllowed` with `payoutsBlockedReason` and `payoutsBlockedMessage` (the sentence the dashboard shows), and `unverifiedRules` (holding days, the rolling cap, the cap used and remaining in the current window). `bankTransferInstructions` is always `null` for API-key and OAuth callers and `canManageFunding` is `false`: the dedicated deposit account numbers are shown only to an interactive Owner or Admin session in the dashboard. ### `POST /api/v1/payouts/preview` **Role:** `Manager` or higher. Computes eligible balances without committing anything. Safe to call repeatedly. Request body (all fields optional): ```json { "programId": null, "since": null, "affiliateId": null } ``` - `programId`: limit the preview to one campaign. - `since`: only consider conversions on or after this ISO 8601 timestamp. - `affiliateId`: preview a single affiliate. The response reports the eligible totals and per-affiliate candidates: - `totalEligibleCents`, `candidateCount`, `candidatesWithStripeConnectCount` - `graceDays` and `homeCurrency` - `eligibleByCurrency`: per-currency eligible totals over every candidate, in each currency's native minor units - `unconvertibleByCurrency`: native minor units per currency that could not be valued in the home currency (no usable stored rate); `totalEligibleInHomeCurrencyCents` leaves them out - `nativeTotalsForRun`: per-currency native eligible sums over the candidates that are ready to pay. This is the exact map the dashboard confirms when it runs the payout, and its key set is the set of currencies the run covers - `fxUnavailableCurrencies`: currencies withheld from the run because no fresh stored reference rate exists; their balances keep accruing and become payable when rates resume - `usdEstimateAtRunRateCents`: a display-only USD estimate of everything payable at today's stored rates (payouts settle in USD; the final figure locks when the run executes) - `fxRunRates`: map of currency code to `{ rate, asOf }`, the stored USD-base reference rate each previewed currency would convert at, as native major units per 1 USD (USD is `1`), absent for the currencies in `fxUnavailableCurrencies`. A bucket's USD value is `round(nativeCents / minorUnitsPerMajor(native) / rate * 100)` - fee fields describing the platform take on the previewed run - `candidates[]`: one entry per affiliate with `affiliateId`, `affiliateEmail`, `currency`, `eligibleCents`, `eligibleConversionIds`, `payoutReady`, and `blockedReason` (why an affiliate cannot be paid yet, for example a missing payout account) A run mints one USD payout per affiliate from the confirmed currencies, converting each commission at the stored run-date rate and stamping the native amount, the rate, and the rate date on every line item (the `lineItems` fields above). ### `POST /api/v1/payouts/run` **Dashboard session only.** Running a payout cannot be triggered with an API key or OAuth token; every API-key caller receives `403 interactive_session_required`. Only a signed-in Owner dashboard session can start a payout run. For reference, the dashboard call accepts `confirmTotalCents` (required safety check), plus optional `affiliateIds`, `programId`, `since`, `currency` (default `"USD"`), `method`, `externalTransactionId`, `notes`, and `conversionIds`. `confirmTotalCents` must equal the server-computed total; if they disagree (for example, a conversion was finalized between preview and run), the request returns `409 { "error": "confirm_total_mismatch" }`; re-preview and retry. Each run fires `payout.created` per recipient, then `payout.paid` or `payout.failed`. **Trust and Safety (2026-09-05).** Four more `409` codes can come back from a run: `payouts_require_active_subscription` (the org is on a trial or has no active paid plan), `unverified_prefund_only` (an unverified org tried to run against a bank-debit mandate; fund by bank transfer or request verification), `unverified_cap_exceeded` (the run would take an unverified org past its rolling cap; the message carries the figures) and `funding_frozen` (payout funding is paused after a bank dispute on a funding charge; the message says to contact support@rekomi.com). A fifth, `high_risk_unverified`, comes back from a run and from the funding setup endpoints when an unverified org's fraud score is at or above the threshold Rekomi's super admins set (60 by default): funding and payouts wait until a person at Rekomi approves the account for funding or verifies it, and the funding status carries `verificationRequired: true` alongside the code. A sixth, `identity_required`, comes back from a run and from the funding endpoints when Rekomi requires an identity check (a fraud score at or above the threshold Rekomi's super admins set for it, or a reviewer's requirement on the brand) until the brand passes Stripe's government ID and selfie check or a super admin clears it; it is checked ahead of `high_risk_unverified`, and the funding status carries `identityRequired: true` alongside the code. A run can also succeed with every row carrying `needsManualReview: true`: the run is held for Rekomi's review before any funding charge, nothing is charged and nothing is paid until it is approved, and the rows move to the normal funding flow (or to `Failed`) when the review is decided. The run's receipt shows `reviewStatus` and, for unverified orgs, `holdUntil`: the settled funding waits out the holding period before affiliates are paid. See [Verification and payout reviews](/docs/brands/payouts#verification-and-payout-reviews). ### `POST /api/v1/payouts/{id}/retry` **Dashboard session only.** Retries a `Failed` payout. Like `/run`, this endpoint rejects every API-key and OAuth caller with `403 interactive_session_required`; only a signed-in Owner dashboard session can retry. Fires the same events as `/run` on success. ### `GET /api/v1/payouts/automatic/preview` **Scope:** `read`. Dry run of the automatic-payout schedule: what the next run would pay, who rolls forward under the minimum, and who has money waiting but no connected account. ```json { "enabled": true, "cadence": "Weekly", "minPayoutCents": 2000, "lastAutomaticPayoutAt": "2026-09-22T14:00:00Z", "nextRunAtUtc": "2026-09-29T14:00:00Z", "wouldPay": { "affiliateCount": 6, "totalCents": 41800, "byCurrency": { "USD": 41800 } }, "belowMinimum": { "affiliateCount": 2, "totalCents": 1500, "byCurrency": { "USD": 1500 } }, "notConnected": { "affiliateCount": 1, "totalCents": 9000, "byCurrency": { "USD": 9000 } } } ``` ### `GET /api/v1/payouts/ledger` **Scope:** `read`. The platform ledger for your organization: brand charges, affiliate payouts and reversals, newest first. Query: `take`, `skip`. ```json [ { "id": "...", "payoutId": "...", "affiliateId": "...", "direction": "Debit", "type": "BrandFunding", "amountCents": 10094, "currency": "USD", "rail": "StripeAch", "railReferenceId": "py_...", "occurredAt": "2026-09-01T09:00:00Z", "reconciliationStatus": "Settled" } ] ``` ### `GET /api/v1/payouts/receipts` **Scope:** `read`. One receipt per funded payout run. Query: `take`, `skip`. ```json [ { "id": "...", "receiptNumber": "R-2026-0031", "currency": "USD", "grossCents": 98000, "platformFeeCents": 2940, "affiliateFeeCents": 1125, "passthroughCents": 0, "netToAffiliatesCents": 96875, "brandTotalCents": 100940, "payoutCount": 5, "billingCase": "BrandFunded", "brandFundingChargeId": "py_...", "brandFundingStatus": "Succeeded", "periodStart": "2026-08-01", "periodEnd": "2026-08-31", "createdAt": "2026-09-01T09:00:00Z", "settledAt": "2026-09-03T00:00:00Z", "reversedAt": null } ] ``` `GET /api/v1/payouts/receipts/{id}` returns the same item under `receipt` plus `organizationName` and `affiliates`, one row per payout (`payoutId`, `affiliateId`, `affiliateName`, `affiliateEmail`, `grossCents`, `platformFeeCents`, `affiliateFeeCents`, `netCents`, `currency`, `status`, `reversed`, and `fxLines` for consolidated multi-currency payouts). `GET /api/v1/payouts/receipts/{id}/receipt.html` renders the receipt as HTML and `GET /api/v1/payouts/receipts/{id}/receipt.csv` downloads the rows. `404 receipt_not_found` otherwise. ### Dashboard-only payout actions `POST /api/v1/payouts/funding/setup`, `POST /api/v1/payouts/funding/bank-transfer/enable`, `POST /api/v1/payouts/funding/bank-transfer/disable`, `POST /api/v1/payouts/verification-request`, `POST /api/v1/payouts/{id}/reverse` and `POST /api/v1/payouts/{id}/reverse-lines` move money or change how it is funded, so they require a signed-in dashboard session. An API key gets `403` on them. ## Audit log **Plan tier:** Pro+ for API-key callers (SIEM / compliance export). JWT Owner dashboard is unrestricted and gets the full schema. API-key callers also get a redacted response (operator IP / UserAgent / internal UserId fields are nulled in JSON; CSV header drops to 8 columns). ### `GET /api/v1/audit-log` **Scope:** `read`. **Role:** `Owner` (JWT only; API keys bypass role checks, but the response is redacted; see below). Query: `action`, `entityType`, `since` (ISO), `until` (ISO), `page` (default 1), `pageSize` (default 50, max 100). API-key callers get a **redacted** payload with operator PII (IPs, internal UserIds, UserAgents) stripped. JWT dashboard callers get the full payload for in-product forensics. The `actorUserId` and `ip` query filters are also disabled for API-key callers (matching the redaction so they can't infer values). ```json // API-key response (redacted) { "page": 1, "pageSize": 50, "total": 1234, "items": [ { "id": "audit-uuid", "action": "program.created", "entityType": "program", "entityId": "0789abcd-...", "actorType": "user", "actorUserId": null, "ip": null, "userAgent": null, "before": null, "after": "{\"id\":\"0789abcd-...\",\"name\":\"Default program\",\"...\":...}", "createdAt": "2026-05-01T12:00:00Z" } ] } ``` `actorType` classifies who or what initiated the action, values are `user` (Clerk-authed brand operator), `api_key` (programmatic call via `rk_live_*`), `mcp` (MCP-relayed call via `rk_mcp_*`), `system` (background job / Hangfire), `admin` (super-admin portal action). It is exposed to API-key callers because it is a category, not PII. ### `GET /api/v1/audit-log/export.csv` **Scope:** `read`. **Role:** `Owner` (same redaction as the JSON endpoint). API-key CSV header (redacted, 8 columns): ```csv id,created_at,action,entity_type,entity_id,actor_type,before,after ``` JWT dashboard CSV header (full, 11 columns): ```csv id,created_at,action,entity_type,entity_id,actor_type,actor_user_id,ip,user_agent,before,after ``` Capped at 10,000 rows. Audit-log entries are append-only; no editing or deletion via the API. ## Dashboard metrics **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. Brand-side aggregate metrics. **Role:** `Manager` for JWT; API keys bypass and read. ### `GET /api/v1/dashboard/summary` **Scope:** `read`. Returns headline KPIs for the active window (`since` query param defaults to 30 days ago). ```json { "totalClicks": 1842, "totalConversions": 87, "totalLeads": 134, "totalEarningsCents": 1734500, "conversionRate": 4.7232, "activeAffiliates": 23, "pendingPayoutsCents": 412000, "homeCurrency": "USD", "earningsUnconvertibleByCurrency": [], "pendingPayoutsUnconvertibleByCurrency": [] } ``` `conversionRate` is the percent value (e.g. `4.72` = 4.72%) rounded to 4 decimals. `Refunded` and `Denied` conversions are excluded from `totalConversions` and `totalEarningsCents`. Money fields are normalized to your `homeCurrency`; any amounts we could not convert (no FX rate for that currency) are reported per currency in `earningsUnconvertibleByCurrency` and `pendingPayoutsUnconvertibleByCurrency` rather than silently folded into the totals. `totalLeads` counts all lead rows in the window (free identified signups plus per-lead campaign leads); see [Track leads and signups](/docs/developers/lead-tracking). ### `GET /api/v1/dashboard/timeseries` **Scope:** `read`. Time-series for charts. Query: `metric` (required: `clicks` | `conversions` | `commissions` | `leads` | `payouts` | `refunds` | `revenue`), `since`, `until`, `granularity` (`day` only today; windows over 400 days bucket monthly), `affiliateId` (optional: scope the series to one affiliate, which is what the Activity view on an affiliate's detail page reads; works for every metric except `payouts`), `programId` (optional: scope to one campaign). Sum the `count` values for a window total, for example one affiliate's click count. `metric=leads` returns the daily count of all lead rows (free identified signups plus per-lead campaign leads); see [Track leads and signups](/docs/developers/lead-tracking). ```json { "metric": "conversions", "granularity": "day", "since": "2026-04-14T00:00:00Z", "until": "2026-05-14T00:00:00Z", "points": [ { "bucket": "2026-04-14", "count": 3 }, { "bucket": "2026-04-15", "count": 5 }, { "bucket": "2026-04-16", "count": 0 } ] } ``` For money metrics (`payouts`, `commissions`, `refunds`, `revenue`) the `count` field is the daily FX-normalized sum in minor units (despite the name); `revenue` is gross sale amounts, `commissions` is affiliate earnings. For `clicks`, `conversions`, and `leads`, `count` is a literal count. ### `GET /api/v1/dashboard/leaderboard` **Scope:** `read`. Top affiliates by metric. Query: `metric` (`clicks` | `conversions` | `earnings` | `epc`), `limit` (default 10, max 50), `since`, `programId` (optional: rank only one campaign's affiliates). ```json [ { "affiliateId": "01234567-...", "fullName": "Jane Doe", "email": "jane@creator.com", "value": 12 }, { "affiliateId": "abcdef00-...", "fullName": "Joe Smith", "email": "joe@example.com", "value": 9 } ] ``` `value` is the per-affiliate conversion count (`metric=conversions`), click count (`clicks`), home-currency `commissionCents` sum (`earnings`), or earnings-per-click in cents (`epc`, FX-normalized commission divided by clicks; only affiliates with at least one click qualify). `email` is the affiliate's email (your own data). ### `GET /api/v1/dashboard/traffic-sources` **Scope:** `read`. Click breakdown by source, channel-classified. Top 20 sources; each row carries a `channel`: `paid` (gclid/msclkid/ttclid or ad referrers; note fbclid alone is classified social, since Facebook stamps it on organic shares too), `search`, `social`, `email`, `ai` (ChatGPT/Perplexity/Claude referrers), `signin` (auth providers), `onsite` (the brand's own site), `portal` (Rekomi-hosted signup pages), `bot`, `referral`, or `direct`. Query: `since`, `until`, `programId` (optional: one campaign), `affiliateId` (optional: one affiliate). ```json [ { "source": "youtube.com", "channel": "social", "count": 540, "percentage": 32.5 }, { "source": "Google Ads", "channel": "paid", "count": 312, "percentage": 18.8 }, { "source": "(direct)", "channel": "direct", "count": 280, "percentage": 16.9 } ] ``` Empty or unparseable referrers bucket as `(direct)`. ### `GET /api/v1/dashboard/export.csv` **Scope:** `read`. CSV export of timeseries (`metric=clicks|conversions|payouts` only, plus `since` / `until`). ```csv date,conversions 2026-04-14,3 2026-04-15,5 ``` ## Insights (analytics) **Plan tier:** Starter+ for API-key callers. JWT dashboard is unrestricted. **Role:** `Manager` for JWT; API keys bypass and read. **Scope:** `read`. Deeper brand-side breakdowns under `GET /api/v1/dashboard/insights/{module}` (alias of `/api/metrics/insights/{module}`). Every module accepts `since`, `until`, `programId` (one campaign), `affiliateId` (one affiliate), and `format=csv` to download the same rows as a CSV (hardened against spreadsheet formula injection). All money is FX-normalized to your home currency; rows whose currency has no usable stored rate for their date are held out and reported per currency (see `overview` below). Built only from data Rekomi already records, so views that attribute a **sale** to a click dimension are **coverage-aware**: only conversions with an originating click carry that dimension. `sale-origin` reports a numeric `coverage` fraction (attributed vs total conversions); `geography` and `devices` bucket sales without a click dimension under `(unknown)` rather than implying full attribution. Modules: - `overview`: `clicks`, `conversions`, `conversionRate`, `epcCents`, `aovCents`, `grossRevenueCents`, `commissionCents`, `refundedConversions`, `refundRate`, `recurringConversions`, `recurringSharePct`, `homeCurrency`. It also returns `leads` (signup count), `clickToLeadRate`, and `leadToSaleRate` (both `0..1` fractions); see [Track leads and signups](/docs/developers/lead-tracking). Money that could not be valued in the home currency (a sale currency with no usable stored rate for the row's date) is held out of `grossRevenueCents`, `aovCents`, `commissionCents`, and `epcCents` rather than counted at face value, and reported per currency in native minor units: `fxUnconvertibleByCurrency` for the revenue rows and `fxUnconvertibleCommissionByCurrency` for the commission rows (each `null` when everything converted). Those maps are the authoritative figures. The older scalar `fxUnconvertibleCents` stays on the wire for compatibility, but it is a sum of native minor units across currencies, a presence signal rather than a money amount: when it is non-zero, read the maps. The CSV download carries the same three as `fx_unconvertible_cents`, `fx_unconvertible_by_currency`, and `fx_unconvertible_commission_by_currency` (each map written as space-separated `CODE:minorUnits` pairs). - `sale-origin`: top referring hosts by `conversions` + `revenueCents`, plus `attributedConversions` / `totalConversions` / `coverage`. - `geography`: per-country `clicks`, `conversions`, `revenueCents`. - `devices`: Mobile / Tablet / Desktop / Bot split of `clicks` + `conversions`. - `utm`: `dimension` query (`source` | `medium` | `campaign`); per-value `clicks`, `conversions`, `revenueCents`. - `time-patterns`: day-of-week (0=Sun) x hour-of-day (UTC) cells of `clicks` + `conversions`. - `time-to-convert`: buckets `<1h` / `1-24h` / `1-7d` / `>7d`. - `funnel`: `clicks` -> `conversions` -> `approved` -> `paid`. Each step is `{ step, count }`; the dashboard shows each step's share of the prior step (computed client-side, not returned as an API field). When lead data exists for the window, a `leads` stage is inserted adaptively between `clicks` and `conversions` (`clicks` -> `leads` -> `conversions` -> `approved` -> `paid`); brands with no leads in the window never see an empty Lead row. - `attribution`: `Coupon` / `Link` / `Other` split of `conversions`, `revenueCents`, `commissionCents`. - `recurring`: one-time vs recurring mix, `mrrByMonth` (from subscription charges), `churnedSubscriptions`. - `customers`: new vs returning (repeat Stripe customer) counts + `revenueCents` (no customer identity returned). - `top-products`: best-selling products by referred revenue. Coverage covers Shopify, Stripe, Paddle, and Chargebee (line items), Gumroad, Lemon Squeezy, Polar, Creem, Dodo (single-product orders), Mollie (when the payment carries order lines), and Braintree subscriptions; Recurly isn't covered yet. `revenueCents` is the pre-discount line subtotal, not the discounted amount actually paid. ```json // GET /api/v1/dashboard/insights/sale-origin { "sources": [{ "source": "youtube.com", "conversions": 12, "revenueCents": 184000 }], "attributedConversions": 41, "totalConversions": 58, "coverage": 0.7069, "homeCurrency": "USD" } ``` Affiliates have a self-scoped, **PII-free** mirror under `GET /api/me/insights/{module}` (`overview`, `sale-origin`, `geography`, `devices`, `links`, `time-patterns`, `utm`, `channels`), each also supporting `affiliateId` (to focus one membership) and `format=csv`. These return only the caller's own counts/commission plus hosts, countries, device classes, link slugs, sub-ids and time buckets, never customer identity. ## Embed (white-label iframe) **Plan tier:** Growth+ (applies to both JWT and API-key auth because embed is a white-label feature, not just API access). Returns 402 `{ "error": "plan_tier_required", required: "Growth", current: "Starter" }` on lower tiers. ### `POST /api/v1/embed/tokens` **Scope:** `read_write`. Mint a per-affiliate iframe token. ```json { "affiliateId": "01234567-...", "allowedOrigins": "https://yourapp.com,https://app.yourapp.com", "rotate": false, "expiresInHours": 24 } ``` - `allowedOrigins`: comma-separated full-URL origins (scheme + host + port). Strict equality on the iframe public endpoint; no wildcards, no `StartsWith` matching. - `rotate`: when `false` and a token already exists for this `(program, affiliate)`, the response returns metadata only (`alreadyExists: true`) and no plaintext token. When `true`, the existing token is replaced and a fresh plaintext is returned. - `expiresInHours`: default `24`. Clamped to `[1, 720]` (30 days max). Response (new token or rotated): ```json { "token": "ZGVtbzAxYWJjZGVm...", "allowedOrigins": "https://yourapp.com,https://app.yourapp.com", "expiresAt": "2026-05-15T12:00:00Z" } ``` Response (token already exists, `rotate=false`): ```json { "id": "embed-token-uuid", "allowedOrigins": "https://yourapp.com,https://app.yourapp.com", "expiresAt": "2026-05-15T12:00:00Z", "alreadyExists": true } ``` The plaintext `token` is returned **exactly once**. Persist it in your application server immediately; Rekomi only stores its SHA-256 hash and cannot recover the plaintext. ### `POST /api/v1/embed/tokens/{id}/revoke` **Scope:** `read_write`. Revokes an outstanding token. Body: `{}`. Response: `{ "ok": true }`. ### `GET /api/embed/public/dashboard?token=...` **Unauthenticated**, but token-and-Origin gated. Used directly by the iframe in the browser. Returns affiliate earnings + brand theming. ```json { "affiliateName": "Jane Doe", "programName": "Default program", "earnedCents": 12450, "paidCents": 9800, "pendingCents": 2650, "brandColor": "#0E7C7B", "brandName": "Your Brand", "recentConversions": [ { "id": "c0nv0001-...", "amountCents": 9900, "commissionCents": 1980, "status": "Approved", "createdAt": "2026-05-10T16:00:00Z" } ] } ``` Failures: - 400: missing `token` query param - 401: `{}` empty body OR `{ "error": "token_expired" }` if the token's `expiresAt` is in the past - 403: `{ "error": "no_allowed_origins_configured" }` | `{ "error": "origin_required" }` | `{ "error": "origin_invalid" }` | `{ "error": "origin_not_allowed" }` The Origin check requires the request to send an `Origin` header AND for that origin to match (scheme + host + port) one entry in `allowedOrigins`. Server-to-server callers without an Origin header are rejected. `earnedCents`, `paidCents`, and `pendingCents` are computed over the affiliate's 50 most recent conversions, not lifetime history. ### `GET /api/v1/embed/dashboard` Unauthenticated. The data the white-label iframe renders, read with the per-affiliate token from `POST /api/v1/embed/tokens`: query `token`. Checked against the token's allowed origins. Returns `affiliateName`, `programName`, `earnedCents`, `paidCents`, `pendingCents`, `brandColor`, `brandName` and `recentConversions` (up to 20 rows of `id`, `amountCents`, `commissionCents`, `status`, `createdAt`). ## S2S tracking **Plan tier:** Starter+. Trialing orgs are always accepted (14-day free trial on any tier). Orgs below Starter tier that are not in trial return 402. ### `POST /api/tracking/s2s` Separate from the bearer-only `/api/v1/*` namespace because S2S adds an HMAC integrity check on top of the bearer. Headers: ```http X-Rekomi-Api-Key: rk_live_xxxx X-Rekomi-Signature: t=1715366423,sig=8f4e2c5b... Content-Type: application/json ``` (`Authorization: Bearer rk_live_xxxx` also works in place of `X-Rekomi-Api-Key`.) Request body: ```json { "externalEventId": "purchase_abc123", "affiliateSlug": "jane-recommends", "amountCents": 9900, "currency": "USD", "customerId": "cus_external_xyz" } ``` Response (fresh): ```json { "ok": true, "conversionId": "c0nv0001-..." } ``` Response (duplicate, replay-safe; the same `externalEventId` was already recorded for this org): ```json { "ok": true, "deduped": true } ``` Mutual exclusivity: while a native processor connection is active (Stripe Connect, Paddle, Braintree, Lemon Squeezy, the Shopify, Wix, and BigCommerce apps, the WooCommerce plugin, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo, or Mollie), the endpoint returns `409` with the matching error code (`stripe_connect_active`, `paddle_connect_active`, `braintree_connect_active`, `lemonsqueezy_connect_active`, `shopify_connect_active`, `wix_connect_active`, `bigcommerce_connect_active`, `wordpress_connect_active`, `chargebee_connect_active`, `polar_connect_active`, `recurly_connect_active`, `gumroad_connect_active`, `creem_connect_active`, `dodo_connect_active`, `mollie_connect_active`) so the same sale is never counted twice. See [S2S tracking](/docs/developers/s2s-tracking) for full signature computation, replay protection, and language examples. ### `POST /api/tracking/lead` Record a **lead** (a free identified signup, an email tied to the referral) rather than a sale. Same auth as `/api/tracking/s2s` (Bearer API key or `X-Rekomi-Api-Key`, plus the `X-Rekomi-Signature` HMAC), and the same `409 *_connect_active` mutual-exclusivity conflicts while a native processor connection is active. A lead mints no commission on its own; a later sale with the same email is credited to the referring affiliate via the email-match fallback even if the click cookie is gone. Request body: ```json { "affiliateSlug": "jane-recommends", "email": "user@example.com", "name": "Jane Doe", "externalEventId": "signup_abc123" } ``` - `affiliateSlug` (required): the affiliate to credit. Matched case-insensitively, so `SERKAN` and `serkan` credit the same affiliate. - `email` (required): the customer email a later sale is matched against. - `name` (optional) and `externalEventId` (optional, used for de-duplication so retries are safe). - `subId` (optional): the channel tag the signup carried; a later sale for the same email inherits it. See [Track leads and signups](/docs/developers/lead-tracking) for the browser `Rekomi.convert()` equivalent, the automatic Stripe / Paddle path, and how email-match attribution works. ## Public storefront (unauthenticated) These do not require an API key; they're rate-limited per IP. ### `GET /api/p/by-handle/{handle}/{campaign?}` Public program info for the affiliate-facing landing page, org-scoped. Anonymous; rate-limited per IP. The `{handle}` is the org's branded subdomain (the `{handle}` in `{handle}.rekomi.com`); the resolver finds the org from the handle, then the campaign within it by slug. Omit `{campaign}` to fetch the org's primary campaign (the one served at the bare `{handle}.rekomi.com` root). This replaces the former global-slug lookup `GET /api/p/{slug}`, which was removed because campaign slugs are unique only per org, not globally. ```json { "id": "0789abcd-...", "organizationName": "Your Brand", "name": "Default program", "slug": "default", "description": "Earn 20% recurring on every annual plan.", "termsUrl": "https://yourbrand.com/affiliate-terms", "customAliasSupported": true } ``` The full response carries more than shown above: it also includes `organizationDescription`, the commission settings (`defaultCommissionType`, `defaultCommissionValue`, `defaultCommissionModel`), `cookieWindowDays`, `recurringMonths`, `attributionModel`, `autoApprove`, `isPubliclyDiscoverable`, `landingPageConfig`, and the brand's logo and favicon URLs, everything the public landing page needs to render. A campaign slug that was retired (renamed) returns `200 { "redirect": true, "campaign": "" }` so the caller can follow to the live slug. Returns `404 Not Found` when the handle does not resolve to an org, or for inactive or archived campaigns. No org GUID is exposed beyond the program `id`. ### `GET /api/p/by-domain?host={host}` Resolve a custom domain (a brand's own domain, e.g. `affiliates.yourbrand.com`; available on every plan) to its org handle and primary campaign slug. Anonymous; rate-limited per IP. ```json { "handle": "yourbrand", "slug": "default" } ``` Returns `404 Not Found` when the host is not a verified custom domain for any org. ### `POST /api/p/id/{programId}/apply` Submit an affiliate application. The path is keyed on the program's GUID (`{programId}`), not its slug, because campaign slugs are unique only per org, not globally. Resolve the GUID first via `GET /api/p/by-handle/{handle}/{campaign?}` (the `id` field). Anonymous; rate-limited to 5 requests/min per IP. Fires `affiliate.created`; if the program has `autoApprove=true`, also fires `affiliate.approved`. Request: ```json { "email": "newaffiliate@example.com", "fullName": "New Affiliate", "website": "https://creator.example.com", "audience": "200k YouTube subscribers in the fitness niche", "whyInterested": "Already an evangelist for the product", "customAlias": "newaff-fit" } ``` All fields except `email` are optional. `customAlias` must match `[a-z0-9-]{3,64}`. Response: the endpoint returns one uniform, anonymized shape to prevent applicant enumeration. It never returns the real affiliate id or link slug; `affiliateId` is always the all-zero GUID and `affiliateLinkSlug` is always `null`. A duplicate application (same email already applied to this program) returns the exact same shape as a fresh application. ```json { "affiliateId": "00000000-0000-0000-0000-000000000000", "status": "Pending", "affiliateLinkSlug": null, "accountExists": false, "email": "newaffiliate@example.com", "message": "Application received." } ``` - `accountExists`: whether the applicant already has a Rekomi affiliate account for this email (used by the public form to route them to sign-in). - `email`: the applicant's own email echoed back. Failures: - `400`: request-validation failure; returns a standard problem details object (`{ type, title, status, errors }`), not the business-rule envelope - `404`: program not found / inactive - `409`: `{ "error": "alias_already_taken" }` or `{ "error": "program_at_capacity", "message": "..." }` ## Headers reference | Header | Direction | Purpose | |---|---|---| | `Authorization: Bearer rk_live_*` | Request | API-key bearer (every authenticated endpoint). | | `X-Rekomi-Api-Key: rk_live_*` | Request (S2S only) | Alternative to `Authorization` on `/api/tracking/s2s`. | | `X-Rekomi-Signature: t=,sig=` | Request (S2S only) | HMAC over `.` using your `rks_*` signing secret. | | `Idempotency-Key: ` | Request | Supported on every `POST` and `PATCH` you might retry (imports, invites, affiliate creation). 60-minute replay window. Same key + same body → original response; same key + different body → 409. | | `Idempotency-Replayed: true` | Response | Set when a response is replayed from a prior idempotent call. | | `X-Rekomi-Signature: t=,v1=` | Outbound (webhook delivery) | HMAC signature on outbound webhook payloads. Use `v1=` for webhook verification (vs `sig=` for inbound S2S; separate code paths). | | `X-Rekomi-Timestamp: ` | Outbound (webhook delivery) | Unix-seconds timestamp the signature is computed over. | | `X-Rekomi-Event: ` | Outbound (webhook delivery) | Event type for routing without body parse. | | `X-Rekomi-Delivery-Id: ` | Outbound (webhook delivery) | De-dup key for retries. | ## Settings ### `GET /api/v1/settings/home-currency` Returns the org's current HomeCurrency, last change timestamp, the supported ISO 4217 allowlist, and whether the caller's plan permits changing it (Pro plan or higher). ```json { "homeCurrency": "USD", "changedAt": null, "supportedCurrencies": [ "AED", "AFN", "ALL", "AMD", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLE", "SOS", "SRD", "SZL", "THB", "TJS", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST", "XAF", "XCD", "XCG", "XOF", "XPF", "YER", "ZAR", "ZMW" ], "canChange": true } ``` ### `PUT /api/v1/settings/home-currency` Change the HomeCurrency. **Pro plan or higher required.** Mutation is idempotent on identical before/after; audit-logged as `org.home_currency_changed`. Historical conversions retain their native currency; only on-read dashboard normalization recomputes against the new home currency. Body: ```json { "homeCurrency": "EUR" } ``` Response: ```json { "homeCurrency": "EUR", "changed": true, "changedAt": "2026-05-18T05:42:00Z" } ``` Failures: - `400 unsupported_currency`: not on the allowlist. - `402 plan_tier_required`: caller is on Starter or Growth. - `403`: caller is not Owner or has unverified email. Full conceptual guide: [Multi-currency for brands](/docs/brands/multi-currency). Ingestion details: [Conversion currency](/docs/developers/conversion-currency). ## CRM integrations **Plan tier:** Starter+ for API-key callers. **Scope:** `read`. ### `GET /api/v1/integrations/crm` Connected CRM and email-platform integrations only; a provider with no connection is simply absent. One row per integration, sorted by `slug`: `provider`, `slug`, `enabled`, `syncCustomers`, `salesAttributionEnabled`, `salesAttributionStatus`, `salesAttributionCheckedAt`, `salesAttributionCredited` and `webhookRegisteredAt`. Per-affiliate sync state lives on `GET /api/v1/affiliates/{id}/crm-sync`. ## Sub-affiliate recruiting **Plan tier:** included on every plan to enable on a campaign. The affiliate-side endpoints (`/me/sub-affiliate/*`) are unrestricted once the campaign is enabled. Bounded 2-tier override: affiliate A recruits B; A earns `B.commission × Program.SubAffiliateCommissionPercent / 100` on every sale B closes. One recruiting level, hard-capped; no chain propagation, never multi-level. Override rows have `amountCents = 0`, `commissionCents > 0`, `attributionMethod = "sub_affiliate_override"`, and `parentAffiliateConversionId` pointing at the parent sale row. Full concept page: [Sub-affiliate recruiting](/docs/brands/sub-affiliate-recruiting). Full developer guide with webhook payloads: [Sub-affiliate API](/docs/developers/sub-affiliate). ### `GET /api/v1/programs/{id}/sub-affiliate-config` **Scope:** `read`. Returns the current per-campaign setting. ```json { "enabled": true, "percent": 10.0 } ``` ### `PUT /api/v1/programs/{id}/sub-affiliate-config` **Scope:** `read_write`. **Role:** `Owner`. **Plan:** included on every plan. **Email verification:** required. Audit-logged as `program.sub_affiliate_config_changed`. ```json { "enabled": true, "percent": 10 } ``` Failures: | HTTP | `error` | Cause | |---|---|---| | 400 | `unsupported_value` | Percent outside `[0, 100]` | | 403 | `email_not_verified` | Owner has not verified email | | 403 | (empty) | Caller is not Owner | | 404 | (empty) | Program not in your org | ### `GET /api/v1/me/sub-affiliate/link?programId={id}` **Scope:** Clerk JWT only (affiliate-side). Returns the calling affiliate's personal recruitment URL. ```json { "affiliateId": "01234567-...", "programId": "0789abcd-...", "programSlug": "default", "url": "https://{handle}.rekomi.com/default?recruited_by=v6media" } ``` The `recruited_by` token is slug-first: the affiliate's account-level `?via=` slug when they have one, else their per-campaign link slug, else the affiliate id GUID. Returns `400 program_id_required` without the query param, `400 sub_affiliate_recruiting_disabled` when the campaign has the toggle off, `403` when the calling affiliate is not `Approved`, `404` when the campaign is not visible to the calling affiliate, `409 portal_not_configured` when the brand has no public portal host. ### `GET /api/v1/me/sub-affiliate/recruits` **Scope:** Clerk JWT only (affiliate-side). Calling affiliate's downline. Cursor-paginated. ```json { "items": [ { "affiliateId": "fed00b00-...", "email": "recruit@example.com", "fullName": "Recruited Person", "status": "Approved", "totalCommissionGeneratedCents": 12400, "overrideEarnedCents": 1240, "currency": "USD", "joinedAt": "2026-05-12T10:00:00Z" } ], "nextCursor": null } ``` ### `GET /api/v1/affiliates/{id}/sub-affiliates` **Scope:** `read`. Brand-side drilldown into any affiliate's downline. Returns a **flat array** (no pagination wrapper). ```json [ { "id": "fed00b00-...", "email": "recruit@example.com", "fullName": "Recruited Person", "status": "Approved", "totalCommissionCents": 12400, "overrideEarnedCents": 1240, "currency": "USD" } ] ``` ### Apply-form addition `POST /api/p/id/{programId}/apply` accepts an optional recruiter reference on the body: `recruitedBy` (string: affiliate id GUID, per-campaign link slug, or account-level `?via=` slug) or the legacy `recruitedByAffiliateId` (GUID). When the reference resolves to an Approved affiliate in the same campaign, the new affiliate's `RecruitedByAffiliateId` is populated. Unresolvable values are silently ignored; self-recruit (same Clerk user or same email) is silently dropped while the application still succeeds; no recruiter is bound while the campaign's recruiting toggle is off. ## Organization profile These endpoints sit at their canonical `/api/organizations/me` path (no `/v1` alias). ### `GET /api/organizations/me` **Scope:** `read`. Returns the calling org's full profile: name, slug, branded subdomain and its change counter/cap, billing email, timezone, brand color, logo and favicon URLs, description, white-label flag, plan tier, subscription status, trial end, payout grace days (and the max allowed at the current tier), and the automatic-payout settings. ```json { "id": "01234567-...", "name": "Acme", "slug": "acme", "subdomain": "acme", "subdomainChangeCount": 0, "subdomainChangesMax": 2, "billingEmail": "billing@acme.com", "timezone": "America/Los_Angeles", "primaryBrandColor": "#3B0764", "logoUrl": "https://cdn.rekomi.com/...", "faviconUrl": null, "description": "Acme makes the world's best widgets.", "whiteLabelEnabled": false, "planTier": "Growth", "subscriptionStatus": "Active", "trialEndsAt": null, "payoutGraceDays": 30, "payoutGraceDaysMax": 90, "automaticPayoutEnabled": false, "automaticPayoutCadence": "Manual", "minPayoutCents": 0, "lastAutomaticPayoutAt": null } ``` The response also carries the brand's industry category and the attribution-param catalog fields used by the dashboard UI. Writes (`PATCH /api/organizations/me`, `POST /api/organizations/me/logo`, etc.) remain JWT-only with Owner role. API keys and MCP tokens cannot mutate the profile. ## Billing state ### `GET /api/billing/state` **Scope:** `read`. Returns the org's plan tier, subscription status, trial end, Stripe linkage status, and a plan-limits snapshot. This is the canonical endpoint to call before recommending an install path: `stripeConnectLinked = true` means the org's checkout webhooks flow to Rekomi automatically (the field name is kept for contract stability; the link is the Rekomi app for Stripe); `false` means the org has not linked Stripe for conversion attribution and needs coupon codes, browser pixel, or S2S to attribute conversions. ```json { "planTier": "Growth", "subscriptionStatus": "Active", "billingProvider": "Stripe", "trialEndsAt": null, "interval": "Monthly", "stripeSubscriptionId": "sub_...", "stripeCustomerLinked": true, "stripeConnectLinked": true, "couponsEnabled": true, "limits": { "affiliates": { "current": 12, "max": null }, "campaigns": { "current": 2, "max": null }, "partnerPayoutsThisMonthCents": { "current": 285000, "max": 1500000 }, "teamSeats": { "current": 1, "max": 5 } }, "stripeConnectedVia": "StripeApp" } ``` `billingProvider` is how the org is billed (for example `Stripe` or `Shopify`). `couponsEnabled` is whether the coupons feature is usable. `stripeConnectedVia` is `StripeApp` when the org's Stripe account is linked through the Rekomi app, or `null` when Stripe is not linked; it always agrees with `stripeConnectLinked`. Stripe Checkout, Portal, and payment-method endpoints remain JWT-only. API keys and MCP tokens cannot open new Checkout sessions or modify billing. ## Affiliate invites Brand-to-affiliate invite flow. Lives at `/api/affiliate-invites` (no `/v1` alias). Bulk endpoint requires Starter+ paid plan; single-invite is available on every plan including Trial. ### `POST /api/affiliate-invites` **Scope:** `read_write`. **Role:** `Manager+`. **Email verification:** required for dashboard (JWT) callers; API keys and MCP tokens are exempt. Creates one invitation. Returns the invite row plus a one-time accept URL. Body: ```json { "programId": "01234567-...", "email": "alex@example.com", "fullName": "Alex Example", "slug": "alex", "personalNoteHtml": "

Hi Alex, would love to have you on our affiliate program.

", "customCommissionType": "Percentage", "customCommissionValue": 30, "autoApprove": true } ``` The optional `slug` field preserves the affiliate's existing slug from a prior platform (drop-in migration use case). When supplied, the accept flow assigns it as the new `AffiliateLink.Slug` if it still passes shape validation (3-64 chars, `[a-z0-9-]`, no leading/trailing hyphen) AND is still globally unique. Otherwise the accept flow falls back to a random slug and surfaces a warning. On the affiliate's first sign-in this becomes their account-level referral slug (the one shared across every campaign and used as `?via={slug}`); if they already have a Rekomi affiliate account, their existing account slug is kept instead. Server returns 409 `slug_already_taken` if the slug is already in use at invite-create time; 400 `slug_reserved` if it matches the reserved-word list (`api`, `admin`, etc.); 400 with `field: "slug"` on charset/length validation failure. The `personalNoteHtml` is sanitized via `RichTextSanitizer` (Ganss.Xss parser-driven) to a strict allowlist: `p`, `br`, `strong`, `b`, `em`, `i`, `u`, `ul`, `ol`, `li`, `a`. The only allowed attribute is `href` on ``, restricted to `https://` URLs. Everything else is stripped. Merge tags supported: `{{affiliate.firstName}}`, `{{program.commission}}`, `{{brand.name}}`. Tokens render after sanitization, so token values cannot smuggle markup. Response: ```json { "invite": { "id": "...", "programId": "...", "programName": "...", "email": "alex@example.com", "state": "Pending", "expiresAt": "2026-05-26T...", "...": "..." }, "acceptUrl": "https://app.rekomi.com/invites/affiliate/" } ``` Failures: - `400 validation_failed` with `field`: missing/invalid email, missing programId. - `409 affiliate_already_exists`: an affiliate with this email already exists in the program. - `409 pending_invites_limit_reached`: org has 500 pending invites. ### `POST /api/affiliate-invites/bulk` **Scope:** `read_write`. **Role:** `Manager+`. **Email verification:** required for JWT callers. **Plan:** Starter+ with `SubscriptionStatus = Active` (Trial users receive 402 `paid_plan_required` by design; bulk email abuse path does not get an MCP free pass). Up to 50 emails per call. Duplicates within the batch and emails already attached to existing affiliates are skipped silently. Accepted invites are emailed at a steady, throttled rate. Two body shapes are accepted. The newer `recipients` shape is preferred for migration use cases since it lets you preserve per-affiliate slugs; the legacy `emails` shape stays available for back-compat. Preferred shape: ```json { "programId": "01234567-...", "recipients": [ { "email": "alex@example.com", "slug": "alex", "fullName": "Alex Example" }, { "email": "ben@example.com", "slug": "ben-r" }, { "email": "carol@example.com" } ], "personalNoteHtml": "

We're migrating from Tapfiliate. Same commission, same campaign.

", "autoApprove": true } ``` Legacy shape (no per-row slug): ```json { "programId": "01234567-...", "emails": ["alex@example.com", "ben@example.com", "carol@example.com"], "personalNoteHtml": "

...

", "autoApprove": true } ``` When both `recipients` and `emails` are supplied, `recipients` wins. Response: ```json { "created": 3, "skipped": 0, "invites": [ /* InviteResponse[] */ ], "slugConflicts": [ { "email": "ben@example.com", "requestedSlug": "alex", "reason": "duplicate_within_batch" } ] } ``` `slugConflicts` reports per-row outcomes when a requested slug was rejected (taken globally, reserved, charset-invalid, or duplicated within the same batch). Those rows still get an invite; they fall back to a fresh random slug at accept-time. Use the array to surface "these 3 affiliates didn't get their preferred slug" feedback in your UI. Failures: - `400 validation_failed`: empty / oversized batch, invalid programId. - `402 paid_plan_required`: org on Trial or below Starter. - `409 pending_invites_limit_reached`: org has 500 pending invites. ### Lifecycle - `GET /api/affiliate-invites` (JWT-only): list invites filtered by `programId` + `state` (Pending / Accepted / Expired / Revoked). - `POST /api/affiliate-invites/{id}/resend` (JWT-only): rotate the token + resend. - `DELETE /api/affiliate-invites/{id}` (JWT-only): revoke a pending invite. - `GET /api/affiliate-invites/by-token/{token}` (anonymous, rate-limited): preview the brand + program for an unaccepted invite. - `POST /api/affiliate-invites/by-token/{token}/accept` (anonymous, rate-limited): accept the invite. Creates the `Affiliate` row (Approved or Pending based on `AutoApprove`). Idempotent: re-accepting returns the original affiliate id. ## Install snippet renderer ### `GET /api/v1/r/install-snippet` **Scope:** `read`. Server-renders the canonical `\n", "programId": "01234567-...", "programSlug": "default", "withPixel": false, "hasCustomAttributionParams": true, "customAttributionParams": "my_aff,brand_ref", "scriptHost": "https://api.rekomi.com", "usingTrackingDomain": false, "emailCapture": true } ``` `emailCapture` in the response says whether the rendered tag carries the attribute, so a caller can print the line above next to the tag without parsing the HTML. `scriptHost` is the host the tag loads from (the brand's verified first-party tracking domain when one is active, otherwise the API host) and `usingTrackingDomain` says which. Failures: | HTTP | `error` | Cause | |---|---|---| | 404 | `no_program_found` | No matching program in the calling org (or no active programs exist yet) | | 409 | `pixel_not_provisioned` | `withPixel=true` but the org has no pixel public key | | 403 | `forbidden` | API key lacks the required scope; the body carries a message naming the scope | XSS-safe by construction: every field embedded in the snippet is server-derived (program id is a Guid, custom params are revalidated through `AttributionParamsService.ParseCustom` against `[a-z0-9_-]{2,32}`, pixel public key comes from DB). There is no caller-controlled input that lands in the HTML output. ## Lead capture (browser) ### `POST /api/v1/r/lead/signup` The public browser endpoint behind `Rekomi.convert()` / `Rekomi.identify()`. Records a **lead** (a free identified signup) against the current referral, posted by the tracking loader via `sendBeacon` / `fetch` keepalive. Public-tracking CORS, rate-limited per IP. To avoid signup enumeration it always returns an opaque `200` regardless of whether a lead was recorded. Body: ```json { "slug": "jane-recommends", "email": "user@example.com", "name": "Jane Doe", "programId": "0789abcd-..." } ``` `slug` (the resolved referral slug) is required; `programId` scopes the lead to one campaign; `name` is optional. The Rekomi loader (`Rekomi.convert`) resolves the referral from the visitor's Rekomi cookie and sends the resolved `slug` (and `programId`) in the body for you. You do not call this raw endpoint directly; use the loader's `Rekomi.convert({ email })`. For Stripe and Paddle, leads are captured automatically with no call at all. See [Track leads and signups](/docs/developers/lead-tracking). The hosted MCP server exposes the equivalent `record_lead` tool (`affiliateSlug`, `email`, optional `name`, `campaignId`); see [MCP](/docs/developers/mcp). ## Attribution params (drop-in migration) These endpoints control which query params the Rekomi loader recognizes for affiliate attribution. The 21 canonical defaults (matching Rewardful, Affonso, Tapfiliate, FirstPromoter, UpPromote, GoAffPro, BixGrow, and other major affiliate platforms) are always recognized; brands can add up to 10 custom params for niche platforms. ### `GET /api/v1/settings/attribution-params/defaults` Public, unauthenticated. Returns the canonical defaults list plus the validation envelope (max custom entries, max total chars, per-param length bounds). Used by the dashboard UI and the marketing site to render the chip grid without hardcoding the catalog. ```json { "defaults": ["via", "ref", "fpr", "aff", "afmc", "rfsn", "sca_ref", "bg_ref", "a_aid", "atp", "affonso", "awc", "sscid", "cjevent", "irclickid", "partner_key", "affid", "affiliate_id", "affcode", "idev", "mbsy"], "maxCustomEntries": 10, "maxTotalChars": 256, "minParamLength": 2, "maxParamLength": 32 } ``` Rate-limited via the `anonymous-tracking` policy. ### `GET /api/v1/settings/attribution-params` **Scope:** `read`. Returns the calling org's current config plus the merged effective priority order. ```json { "custom": "myparam,brand_ref", "defaults": ["via", "ref", "fpr", "aff", "..."], "effectiveOrder": ["myparam", "brand_ref", "via", "ref", "..."], "maxCustomEntries": 10, "maxTotalChars": 256 } ``` `custom` is null when the brand has not configured any custom params; in that case `effectiveOrder` equals `defaults`. Custom entries are deduplicated against defaults at read time so the same param never appears twice in `effectiveOrder`. ### `PATCH /api/v1/settings/attribution-params` **Scope:** `read_write`. **Role:** `Owner` (bypassed for `api_key` auth scheme per standard role-attribute behavior). **Email verification:** required for JWT callers (bypassed for `api_key`). Audit-logged as `org.attribution_params_changed` with before / after. Body: ```json { "custom": "myparam,brand_ref" } ``` Pass `null` or `""` to clear the override and revert to defaults-only. Failures: | HTTP | `error` | Cause | |---|---|---| | 400 | `attribution_params_too_long` | Body exceeds 256 chars | | 400 | `too_many_attribution_params` | More than 10 entries | | 400 | `invalid_attribution_param` | An entry does not match `[a-z0-9_-]{2,32}` (the response includes the rejected entries in an `invalid` array) | | 403 | (empty) | Not Owner role and not API key | | 404 | (empty) | Org not found | Each successful change fires a single audit log row capturing both the prior value and the new value. The loader picks up the new config on the next page load on the brand's site (no cache delay beyond the loader response's 1-hour `Cache-Control`). ## Where to go next - [Authentication](/docs/developers/authentication): generate and manage keys. - [Webhooks](/docs/developers/webhooks): receive events, verify signatures, handle retries. - [S2S tracking](/docs/developers/s2s-tracking): non-Stripe conversion ingest. - [Track leads and signups](/docs/developers/lead-tracking): capture free signups and email-match a later sale. - [Embed](/docs/developers/embed): host the affiliate dashboard on your domain. - [MCP server](/docs/developers/mcp): AI-assistant integration with 61 tools including an install assistant. - [Zapier](/docs/developers/zapier): no-code automations across 8,000+ apps. 9 triggers, 9 actions, 4 searches. - [Conversion currency](/docs/developers/conversion-currency): ISO 4217 allowlist, error shape, FX behavior. --- # Server-to-server tracking Source: https://rekomi.com/docs/developers/s2s-tracking > HMAC-signed conversion ingest for any payment gateway beyond the native Stripe, Paddle, Braintree, Shopify, Wix, BigCommerce, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connections. --- title: Server-to-server tracking description: HMAC-signed conversion ingest for any payment gateway beyond the native Stripe, Paddle, Braintree, Shopify, Wix, BigCommerce, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connections. --- Use the S2S tracking endpoint to record a conversion from any payment gateway, or from any conversion that never flows through a connected processor at all. Stripe, Paddle, Braintree, Shopify, Wix, BigCommerce, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connect natively, so no relay is needed for those. For everything else, including mobile in-app purchases, server-side trial conversions, and custom event sources, this is the path: if you can POST an HMAC-signed request from your backend, Rekomi records the conversion. Rekomi treats S2S conversions the same as natively-tracked ones: attribution, refund handling, payouts. **Plan tier required: Starter or higher.** Trialing orgs (14-day free trial) are always accepted. An org whose plan tier is below Starter and is not trialing returns 402 with `{ "error": "plan_tier_required", "required": "Starter", "current": "", ... }`. ## Works with any payment gateway S2S is the universal path: if your backend can POST an HMAC-signed request, Rekomi records the conversion, no matter which payment gateway sits behind it (or none at all). It is deliberately not tied to a list of supported platforms, so **anything that is not natively connected works here**, including custom checkouts, mobile in-app purchases, and server-side trial conversions. **Already on a natively-supported gateway?** Stripe, Paddle, Braintree, Shopify, Wix, BigCommerce, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, and Mollie connect directly in a few clicks with no relay to build. If you use one of those, prefer the [native integration](/integrations); reach for S2S for **every other gateway**. The platforms below just ship with ready-made, step-by-step S2S walkthroughs in the [Setup checklist](/dashboard/setup/install). They are convenience guides, not the limit; any backend works (see below): } title="Shopify" href="/docs/brands/install/shopify" description="Recommended for production. Subscribe to order_paid, relay handler calls /api/tracking/s2s." /> } title="Gumroad" href="/docs/brands/install/gumroad" description="Prefer the native Gumroad connection (one click, no relay). This legacy Ping URL relay remains for unconnected sellers." /> } title="Rails" href="/docs/brands/install/rails" description="ApplicationController concern or background worker. OpenSSL::HMAC for signing." /> } title="Django" href="/docs/brands/install/django" description="View or Celery task. hmac.new(secret, ...).hexdigest() for signing." /> **Any backend works.** S2S is just an HTTPS POST with HMAC-SHA256 signing. The Node.js example below ports 1:1 to Python, Ruby, Go, PHP, .NET, Elixir, or anything that can make HTTP requests and compute HMAC. Use this path whenever you can keep a secret server-side. ## Endpoint ``` POST /api/tracking/s2s ``` Production: `https://api.rekomi.com/api/tracking/s2s` Staging: `https://rekomi-api-staging-owaet.ondigitalocean.app/api/tracking/s2s` ## Headers ```http Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxx X-Rekomi-Signature: t=1715366423,sig=8f4e2c5b... Content-Type: application/json ``` Authentication accepts either header (Authorization takes precedence when both are sent): - **`Authorization: Bearer rk_live_*`** (recommended) or `Bearer rk_test_*`: your bearer API key. - **`X-Rekomi-Api-Key: rk_live_*`**: legacy header. Same key, same semantics. Plus: - `X-Rekomi-Signature` (required): HMAC-SHA256 signature in `t=,sig=` format. Computed as `HMAC-SHA256(signing_secret, ".")`. The signing secret is the separate `rks_...` value you got when creating the API key, not the bearer. ## Request body ```json { "externalEventId": "purchase_abc123", "affiliateSlug": "jane-recommends", "amountCents": 9900, "currency": "USD", "customerId": "cus_external_xyz" } ``` - `externalEventId` (required): your unique identifier for this conversion. Used for de-duplication. - `affiliateSlug` (required): the affiliate to credit. From the affiliate's tracking URL. Matched case-insensitively, so `SERKAN` and `serkan` credit the same affiliate. - `amountCents` (required): the conversion amount in minor units of the `currency` you send (cents for USD, whole yen for JPY, thousandths for KWD, BHD, and OMR). Positive integer. Upper bound 1,000,000,000,000 minor units, a currency-blind sanity bound that sits beyond any plausible single sale in every supported currency. - `currency` (optional): ISO 4217 code, exactly 3 letters. Defaults to `USD` when omitted or blank; any other value must be a supported 3-letter code or the request is refused with `400 unsupported_currency`. Normalized to uppercase server-side, so `usd` and `USD` are equivalent. - `customerId` (optional): your customer identifier. Useful for joining back to your own database. Send one stable id per real customer and never a placeholder: Rekomi uses it to recognise a returning customer, so the campaign's "months per customer" commission window is keyed on it, and a shared or constant value would make unrelated buyers look like one customer. - `customerName` (optional): the customer's display name, shown on the conversion row in the dashboard. - `customerEmail` (optional): the customer's email. - `customerAvatarUrl` (optional): an https avatar image URL; when omitted, an avatar is derived from `customerEmail`. - `subId` (optional): the channel tag the customer's visit carried (`?sub=yt` on the affiliate's link), read from `window.Rekomi.subId` or the `_rkmisub` cookie at checkout. Normalized server-side (lowercase letters, numbers, dots, hyphens, underscores, at most 64 characters). When omitted, the sale carries no tag (the slug postback stores only what you send); a postback that goes through the email-match path does inherit a lead's tag. See [Sub-IDs](/docs/developers/pixel-tracking#sub-ids). Commission rules apply exactly as on a gateway webhook: the affiliate's per-affiliate override, then their partner group rate, then the performance tier their referred revenue has crossed, then the campaign default (see [Commission models](/docs/brands/commission-models)). The body carries no coupon code, so a coupon's own commission rate is the one rung that does not come into play here. ### Currency The `currency` field accepts a 135+ code ISO 4217 allowlist (every Stripe presentment currency plus the three-decimal Gulf codes), matched case-insensitively (`usd` and `USD` are equivalent). An omitted or blank `currency` defaults to `USD`. Any other value must be a supported 3-letter code: a code that is not on the allowlist, and any value that is not exactly 3 letters (`EURO`, `YEN`, `US$`), returns `400 unsupported_currency` and records nothing, with one transitional exception: `ANG` records as `XCG`, its one-to-one successor. Send the real ISO code of the sale; a mislabelled currency is never coerced to USD. See [Conversion currency](/docs/developers/conversion-currency) for the full list, error response shapes, and how the org's display home currency is set. ## Response (success) Two success shapes: fresh conversion vs. duplicate (replay-safe). Fresh: ```json { "ok": true, "conversionId": "c0nv0001-..." } ``` Duplicate (the same `externalEventId` was already seen for this org): ```json { "ok": true, "deduped": true } ``` The `deduped` shape does not echo a `conversionId`; the original conversion still owns the event. Treat both as success on the caller side. Retries are safe indefinitely. ## Response (error) Error responses use one of four shapes depending on what failed: **401 Unauthorized**: empty body. Triggered by: - missing or invalid bearer key - missing, malformed, or wrong HMAC signature - timestamp more than 300 seconds out of sync **409 Conflict**: your organization already tracks conversions automatically through a native connection, so S2S ingest is disabled to prevent the same sale being counted twice. JSON body with an `error` code and a human-readable `message`. There is one code per native connection, and the list grows with the roster: - `stripe_connect_active` - `bigcommerce_connect_active` - `wix_connect_active` - `wordpress_connect_active` (the WooCommerce plugin) - `braintree_connect_active` - `paddle_connect_active` - `lemonsqueezy_connect_active` - `shopify_connect_active` - `chargebee_connect_active` - `polar_connect_active` - `recurly_connect_active` - `gumroad_connect_active` - `creem_connect_active` - `dodo_connect_active` - `mollie_connect_active` Disconnect the named gateway from its page under the **Connect payment gateway** step (for Shopify, Wix, and BigCommerce, uninstall the Rekomi app from that platform's dashboard; for WooCommerce, disconnect the Rekomi plugin on your WordPress site) if you genuinely want to switch to S2S. The same conflicts apply to `POST /api/tracking/lead` (see below). **402 Plan tier required**: JSON body with an upgrade prompt: ```json { "error": "plan_tier_required", "required": "Starter", "current": "None", "trialing": false, "message": "S2S tracking requires Starter tier or higher. Upgrade in Settings > Billing." } ``` **400 Bad Request**: JSON body with just an `error` code: ```json { "error": "amount_out_of_range" } ``` The full list of 400 codes: - `external_event_id_required`: missing/empty `externalEventId` - `affiliate_slug_required`: missing/empty `affiliateSlug` - `amount_out_of_range`: `amountCents` is ≤ 0 or above 1,000,000,000,000 minor units - `unsupported_currency`: `currency` was sent (not blank) and is not a supported 3-letter code, whether an unknown code or a value of another length such as `EURO` (body also echoes the offending `currency`) - `invalid_body`: JSON parse failure - `no_active_program`: the campaign this affiliate belongs to is inactive or suspended - `affiliate_not_found`: `affiliateSlug` does not match any link in your org - `affiliate_not_active`: the affiliate exists but is not currently approved, so their slug no longer earns - `not_a_sale_campaign`: the affiliate's campaign rewards clicks or leads, not sales, so a sale postback is rejected ## Signature computation The signature is HMAC-SHA256 of the string `{unix-seconds}.{raw-body-bytes}` using your plaintext signing secret as the key. Hex-encode the result. ```ts import crypto from "node:crypto"; function sign(body: string, signingSecret: string): string { const t = Math.floor(Date.now() / 1000); const payload = `${t}.${body}`; const sig = crypto.createHmac("sha256", signingSecret).update(payload).digest("hex"); return `t=${t},sig=${sig}`; } ``` **Sign the exact bytes you send.** The server HMACs the raw request body it receives, so you must sign and send the identical byte array. The #1 cause of a `401` on an otherwise-correct request is serializing the body twice (signing one JSON string, sending another whose whitespace or key casing differs). Serialize once to bytes, sign those bytes, send those bytes. For example, in .NET: `var body = JsonSerializer.SerializeToUtf8Bytes(payload);` then HMAC over `"{t}." + body` and `new ByteArrayContent(body)`, never a second `JsonSerializer.Serialize(payload)` call for the request content. ## Replay protection The server rejects signatures with `t=` more than 300 seconds (5 minutes) old, or more than 300 seconds in the future. Keep your server's clock synced via NTP. The server also rejects duplicate `(externalEventId, organization)` pairs. The second call with the same external event id returns the original conversion with `deduped = true`. Safe to retry indefinitely. ## Examples The same request in four languages; every other stack follows the identical shape (serialize once, HMAC those exact bytes, send those bytes). ```ts import crypto from "node:crypto"; const BEARER = process.env.REKOMI_API_KEY!; // rk_live_... const SIGNING = process.env.REKOMI_SIGNING_SECRET!; // rks_... async function logConversion(input: { externalEventId: string; affiliateSlug: string; amountCents: number; currency?: string; customerId?: string; }) { const body = JSON.stringify(input); 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/s2s", { method: "POST", headers: { "X-Rekomi-Api-Key": BEARER, "X-Rekomi-Signature": `t=${t},sig=${sig}`, "Content-Type": "application/json", }, body, }); if (!res.ok) throw new Error(`Rekomi S2S failed: ${res.status} ${await res.text()}`); return await res.json(); } ``` ```python import hmac, hashlib, time, json, requests BEARER = "rk_live_..." SIGNING = "rks_..." def log_conversion(external_event_id, affiliate_slug, amount_cents, currency="USD"): body = json.dumps({ "externalEventId": external_event_id, "affiliateSlug": affiliate_slug, "amountCents": amount_cents, "currency": currency, }, separators=(",", ":")) t = int(time.time()) sig = hmac.new(SIGNING.encode(), f"{t}.{body}".encode(), hashlib.sha256).hexdigest() r = requests.post("https://api.rekomi.com/api/tracking/s2s", data=body, headers={ "X-Rekomi-Api-Key": BEARER, "X-Rekomi-Signature": f"t={t},sig={sig}", "Content-Type": "application/json", }) r.raise_for_status() return r.json() ``` ```php $externalEventId, 'affiliateSlug' => $affiliateSlug, 'amountCents' => $amountCents, 'currency' => $currency, ], JSON_UNESCAPED_SLASHES); $t = time(); $sig = hash_hmac('sha256', $t . '.' . $body, $signing); $ch = curl_init('https://api.rekomi.com/api/tracking/s2s'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'X-Rekomi-Api-Key: ' . $bearer, 'X-Rekomi-Signature: t=' . $t . ',sig=' . $sig, 'Content-Type: application/json', ], ]); $res = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); if ($status >= 400) { throw new RuntimeException("Rekomi S2S failed: {$status} {$res}"); } return json_decode($res, true); } ``` ```ruby require "net/http" require "openssl" require "json" BEARER = ENV.fetch("REKOMI_API_KEY") # rk_live_... SIGNING = ENV.fetch("REKOMI_SIGNING_SECRET") # rks_... def log_conversion(external_event_id:, affiliate_slug:, amount_cents:, currency: "USD") body = JSON.generate({ externalEventId: external_event_id, affiliateSlug: affiliate_slug, amountCents: amount_cents, currency: currency, }) t = Time.now.to_i sig = OpenSSL::HMAC.hexdigest("SHA256", SIGNING, "#{t}.#{body}") uri = URI("https://api.rekomi.com/api/tracking/s2s") res = Net::HTTP.post(uri, body, { "X-Rekomi-Api-Key" => BEARER, "X-Rekomi-Signature" => "t=#{t},sig=#{sig}", "Content-Type" => "application/json", }) raise "Rekomi S2S failed: #{res.code} #{res.body}" unless res.is_a?(Net::HTTPSuccess) JSON.parse(res.body) end ``` ## Tracking leads (signups) For **Stripe and Paddle**, leads are captured automatically, with no extra code, so you usually do not need this endpoint there. Use it when your gateway is **not natively connected** and you drive tracking from your backend: post a free signup (not a sale) to `POST /api/tracking/lead` with the **same Bearer API key + `X-Rekomi-Signature` HMAC auth** shown above. Note the mutual-exclusivity conflicts above apply here too: while a native connection (Stripe, Paddle, Braintree, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments, Mollie, the Shopify app, or the Wix app) is active, the lead endpoint returns the matching `*_connect_active` 409, and the browser `Rekomi.convert()` call is the lead path instead. A lead ties a customer email to the referral before payment, so a later sale is credited even if the click cookie is gone. The body also takes an optional `subId` (the channel tag), which the later sale inherits. See [Track leads and signups](/docs/developers/lead-tracking) for the body shape, the browser `Rekomi.convert()` equivalent, and how the email-match fallback works. ## When to use S2S vs the edge redirect - **S2S** when conversions originate server-side (mobile app purchases, custom checkout flows, server-side trial conversions). - **Edge redirect** (the `/r/{slug}` tracking link with first-party cookie) when conversions originate from a browser click on a referral link. - **Coupon codes** when you cannot run any server-side code; give each affiliate a unique discount code and attribution happens at checkout with nothing on your site. For most subscription products, S2S is the more reliable path because it does not depend on the browser cookie surviving across the entire conversion funnel. ## Refunding conversions S2S conversions are refunded through `POST /api/tracking/refund`; conversions from a natively connected processor are refunded automatically by that processor's refund webhook and do not need this endpoint. ``` POST /api/tracking/refund ``` Same HMAC + bearer auth model as the S2S endpoint above. Body: ```json { "externalEventId": "purchase_abc123", "refundAmountCents": 9900 } ``` - `externalEventId` (required): the original conversion's external event id (camelCase, same convention as the S2S body). - `refundAmountCents` (optional): partial refund in minor units of the conversion's currency. Omit to refund whatever remains unrefunded (the full original amount when no partial refund has been recorded); after a partial refund, an omitted amount refunds the remainder. Response (full refund): ```json { "ok": true, "conversionId": "c0nv0001-...", "refundedAmountCents": 9900, "cumulativeRefundedAmountCents": 9900, "isFullRefund": true } ``` Partial refunds accumulate: subsequent refunds add to `cumulativeRefundedAmountCents` and only flip the conversion to status `Refunded` once the cumulative total reaches the original amount. Commission reversal is proportional; refunding 40% of the order amount reverses 40% of the commission. ### Error responses This endpoint is also reachable at the alias route `POST /api/v1/tracking/refund` with identical behavior. | HTTP | `error` | Cause | |---|---|---| | 400 | `invalid_body` | JSON parse failure | | 401 | (empty body) | Missing / invalid bearer / HMAC signature / timestamp out of sync | | 402 | `plan_tier_required` | Org below Starter and not trialing | | 413 | `payload_too_large` | Request body exceeds 4096 bytes | | 404 | `conversion_not_found` | No conversion matches the external event id for this org | | 422 | `external_event_id_required` | Missing / empty | | 422 | `refund_amount_invalid` | ≤ 0 | | 422 | `already_fully_refunded` | The conversion has already been refunded in full, so nothing remains, whether the amount is omitted or sent | | 422 | `amount_exceeds_original` | This refund would push cumulative refunded past the conversion amount | ### Signature Identical to the S2S signing computation. Reuse the same HMAC helper. ```ts const body = JSON.stringify({ externalEventId: "purchase_abc123", refundAmountCents: 9900, }); const t = Math.floor(Date.now() / 1000); const sig = crypto.createHmac("sha256", SIGNING).update(`${t}.${body}`).digest("hex"); await fetch("https://api.rekomi.com/api/tracking/refund", { method: "POST", headers: { "Authorization": `Bearer ${BEARER}`, "X-Rekomi-Signature": `t=${t},sig=${sig}`, "Content-Type": "application/json", }, body, }); ``` ### Idempotency and replays Refund replays for an already-fully-refunded conversion return `422 already_fully_refunded` rather than re-decrementing commission. Partial-refund overflows are clamped: if `refundAmountCents` plus prior refunds would exceed the conversion amount, the request fails with `422 amount_exceeds_original`. Always send `refundAmountCents` equal to the actual refund issued by your processor. --- # Sub-affiliate recruiting API Source: https://rekomi.com/docs/developers/sub-affiliate > Endpoints, override conversion shape, webhook payloads, and apply-form semantics for the bounded 2-tier sub-affiliate model. --- title: Sub-affiliate recruiting API description: Endpoints, override conversion shape, webhook payloads, and apply-form semantics for the bounded 2-tier sub-affiliate model. --- This is the developer-side surface for [sub-affiliate recruiting](/docs/brands/sub-affiliate-recruiting): the brand-side concept page covers the "what" and "why"; this page covers the wire shapes. There are five endpoints, one new conversion-row shape, one webhook variation, and two apply-form fields. Everything is bounded to one recruiting level, hard-capped; there is no chain propagation. ## Endpoints All five are mounted under `/api/v1/*`. The three brand-side endpoints accept the standard `Authorization: Bearer rk_live_*` API key auth, plus the same JWT path for the in-product dashboard; the two `/me/sub-affiliate/*` endpoints are affiliate-side and accept the affiliate JWT only, never an API key. ### `GET /api/v1/programs/{id}/sub-affiliate-config` **Scope:** `read`. Returns the current sub-affiliate config for a campaign. ```bash curl -sS https://api.rekomi.com/api/v1/programs/0789abcd-.../sub-affiliate-config \ -H "Authorization: Bearer $REKOMI_API_KEY" ``` Response: ```json { "enabled": true, "percent": 10.0 } ``` - `enabled`: whether the recruiting feature is on for this campaign. - `percent`: override percent on parent commission. Range 0 to 100. When `enabled` is false, this field is still returned (it just doesn't fire on new conversions). ### `PUT /api/v1/programs/{id}/sub-affiliate-config` **Scope:** `read_write`. **Role:** `Owner` (on JWT). **Plan:** included on every plan. Email verification required. Audit-logged as `program.sub_affiliate_config_changed`. ```bash curl -sS -X PUT https://api.rekomi.com/api/v1/programs/0789abcd-.../sub-affiliate-config \ -H "Authorization: Bearer $REKOMI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "percent": 10 }' ``` Response (200): ```json { "enabled": true, "percent": 10.0 } ``` Failures: | HTTP | `error` | Cause | |---|---|---| | 400 | `body_required` | Missing or empty JSON body | | 400 | `unsupported_value` | Percent outside `[0, 100]` or not a number | | 403 | `email_verification_required` | Dashboard caller has not verified their email | | 403 | (empty) | Caller is not Owner | | 404 | (empty) | Program not in your org | ### `GET /api/v1/me/sub-affiliate/link?programId={id}` **Scope:** `read`. Affiliate-side. Returns the calling affiliate's personal recruitment link for a campaign. Auth is the affiliate's `/a` JWT, not a brand API key. ```bash curl -sS "https://api.rekomi.com/api/v1/me/sub-affiliate/link?programId=0789abcd-..." \ -H "Authorization: Bearer $AFFILIATE_JWT" ``` Response: ```json { "affiliateId": "01234567-...", "programId": "0789abcd-...", "programSlug": "default", "url": "https://your-brand.example.com/default?recruited_by=v6media" } ``` The `recruited_by` token is slug-first: it is the affiliate's account-level `?via=` slug when they have one (`v6media` above), otherwise their per-campaign link slug, and only the raw affiliate id GUID when neither slug exists. Failures: | HTTP | `error` | Cause | |---|---|---| | 403 | `email_verification_required` | Calling affiliate has not verified their email (checked before everything else) | | 400 | `program_id_required` | Missing `programId` query param | | 400 | `sub_affiliate_recruiting_disabled` | Campaign has `enabled = false` | | 403 | (empty) | Calling affiliate is not `Approved` on the campaign | | 404 | (empty) | Campaign not visible to the calling affiliate (not a member) | | 409 | `portal_not_configured` | The brand has no public portal host to build the URL on | ### `GET /api/v1/me/sub-affiliate/recruits` **Scope:** `read`. Affiliate-side. Lists the calling affiliate's downline. Cursor-paginated. ```bash curl -sS "https://api.rekomi.com/api/v1/me/sub-affiliate/recruits?take=50" \ -H "Authorization: Bearer $AFFILIATE_JWT" ``` Response: ```json { "items": [ { "affiliateId": "fed00b00-...", "email": "recruit@example.com", "fullName": "Recruited Person", "status": "Approved", "totalCommissionGeneratedCents": 12400, "overrideEarnedCents": 1240, "currency": "USD", "joinedAt": "2026-05-12T10:00:00Z" } ], "nextCursor": null } ``` - `totalCommissionGeneratedCents`: this recruit's own commission income (excludes override rows so it never double-counts when the recruit later recruits someone else). - `overrideEarnedCents`: total override the calling affiliate has earned from this recruit, in `currency`. - Pending applicants and rejected applications do not appear in the list; a recruit shows up once the brand approves them (recruits later paused or banned stay listed with their history). Only Approved recruits accrue override income. ### `GET /api/v1/affiliates/{id}/sub-affiliates` **Scope:** `read`. Brand-side drilldown: lets a brand operator inspect any affiliate's downline. Returns a **flat array** (no pagination wrapper) with slightly different field names than the affiliate-side endpoint. ```bash curl -sS "https://api.rekomi.com/api/v1/affiliates/01234567-.../sub-affiliates" \ -H "Authorization: Bearer $REKOMI_API_KEY" ``` ```json [ { "id": "fed00b00-...", "email": "recruit@example.com", "fullName": "Recruited Person", "status": "Approved", "totalCommissionCents": 12400, "overrideEarnedCents": 1240, "currency": "USD" } ] ``` ## Override conversion row When affiliate A recruits affiliate B, and B closes a sale, two `Conversion` rows are written: the parent commission row first, then the override row in a separate idempotent write. If the override write ever fails, it is backfilled automatically the next time the same sale event is delivered, so no override is lost. 1. **Parent commission row** for B. Looks like a normal conversion. `AmountCents` is the sale, `CommissionCents` is B's commission, `AttributionMethod` is whatever path attributed the sale (for example `coupon` or `metadata_or_customer`). `ParentAffiliateConversionId` is `null`. 2. **Override row** for A. `AmountCents = 0`, `CommissionCents = (B.commission × Program.SubAffiliateCommissionPercent / 100)`, rounded to the nearest minor unit with banker's rounding (round half to even, `MidpointRounding.ToEven`). `AttributionMethod = "sub_affiliate_override"`. `ParentAffiliateConversionId` points at the parent row's id. Example conversion list query showing both rows: ```bash curl -sS "https://api.rekomi.com/api/v1/conversions?programId=0789abcd-...&take=50" \ -H "Authorization: Bearer $REKOMI_API_KEY" ``` Response (relevant fields only): ```json { "items": [ { "id": "c0nv0parent-...", "affiliateId": "01234567-B-...", "programId": "0789abcd-...", "amountCents": 10000, "currency": "USD", "commissionCents": 2000, "attributionMethod": "coupon", "parentAffiliateConversionId": null, "status": "Approved" }, { "id": "c0nv0override-...", "affiliateId": "01234567-A-...", "programId": "0789abcd-...", "amountCents": 0, "currency": "USD", "commissionCents": 200, "attributionMethod": "sub_affiliate_override", "parentAffiliateConversionId": "c0nv0parent-...", "status": "Approved" } ], "nextCursor": null, "hasMore": false } ``` Notes: - Override rows always have `amountCents = 0`. The sale amount lives on the parent row; duplicating it on the override would double-count revenue on dashboards. - Override rows always have `commissionCents > 0`. If the computed override rounds to zero (very small parent commission × very small percent), the row is not created. - `parentAffiliateConversionId` is a foreign key to the same `Conversion` table. Use it to join override rows back to the sale that generated them. ## Refund cascade Whenever the parent conversion is refunded (partially or fully), through any refund path (payment-processor webhooks, the refund tracking endpoint, or the S2S refund endpoint), Rekomi computes the proportional fraction of the parent commission that was clawed back and applies the same fraction to the override. - Full refund of the parent: override is fully reversed. - Partial refund (e.g., 40% of the sale amount): both the parent commission and the override are reduced by 40%. Both rows fire `conversion.refunded` webhook events. Consumers tracking lifetime earnings should subtract the `commissionReversedCents` from their running totals on both rows. ## Deny and invalidate cascade Status changes on the parent conversion cascade to its unpaid override the same way refunds do. The override is only ever adjusted while it is still unpaid; once an override has been paid out it is left untouched. - **Deny.** Denying a parent conversion also denies its unpaid override, so a rejected sale never leaves recruiter income behind. - **Invalidate.** Fraud-quarantining a parent conversion quarantines its unpaid override as well. - **Validate.** Restoring a previously invalidated parent restores the override at its original amount. ## Webhook payloads ### `conversion.created` (override row) Same envelope as the normal `conversion.created` event. The body distinguishes override rows via `attributionMethod`: ```json { "id": "evt_9f3c1a7b2e4d6f80a1b3c5d7e9f0a2b4", "type": "conversion.created", "createdAt": "2026-05-12T03:14:25.000Z", "organizationId": "b7e2c9a4-3f1d-4e8a-9c2b-6d5f0a1e3c7b", "data": { "conversionId": "c0nv0override-...", "affiliateId": "01234567-A-...", "programId": "0789abcd-...", "amountCents": 0, "currency": "USD", "commissionCents": 200, "status": "Pending", "attributionMethod": "sub_affiliate_override", "parentAffiliateConversionId": "c0nv0parent-...", "externalEventId": "in_1abc23DEF456:override:3f9c2a1b8e4d6f70a1b3c5d7e9f0a2b4", "type": "OneTime" } } ``` The override row's `externalEventId` is the parent sale's external event id with an override suffix that also embeds the recruiter's id as 32 hex characters (`:override:<32-hex-recruiter-id>`); very long parent ids are replaced by a hash prefix. It is stable across redeliveries of the same sale event, so consumers can use it as an idempotency key when recording override income. ### `conversion.refunded` (override row) Emitted on every cascade iteration where the override's `RefundedAmountCents` was incremented. `refundedAmountCents` and `commissionReversedCents` are the THIS-event clawback; `cumulativeRefundedAmountCents` is the lifetime total on the row; `isFullRefund` is `true` only on the iteration that crossed the override into fully-refunded state. Carries the parent linkage + attribution-method so consumers can route override-row events separately. ```json { "id": "evt_2b4d6f80a1b3c5d7e9f0a2b41c3e5d70", "type": "conversion.refunded", "createdAt": "2026-05-15T11:00:00Z", "organizationId": "b7e2c9a4-3f1d-4e8a-9c2b-6d5f0a1e3c7b", "data": { "conversionId": "c0nv0override-...", "affiliateId": "01234567-A-...", "programId": "0789abcd-...", "refundedAmountCents": 200, "cumulativeRefundedAmountCents": 200, "commissionReversedCents": 200, "currency": "USD", "isFullRefund": true, "refundedAt": "2026-05-15T11:00:00Z", "parentAffiliateConversionId": "c0nv0parent-...", "attributionMethod": "sub_affiliate_override" } } ``` ### Warning for webhook consumers If your handler filters `conversion.created` events by `amountCents > 0` to drop "zero-revenue" events, you will silently lose all override events. Override rows are real commission obligations with `commissionCents > 0` but always `amountCents = 0`. Better filters: - Filter on `status` (e.g., only act on `Approved` or `Paid`). - Filter on `commissionCents > 0` if you want anything that owes the affiliate money. - Match on `attributionMethod` if you want to route override rows to a different downstream pipeline (e.g., a separate accounting bucket for recruiter income vs. direct-sale commission). ## Apply form: `recruitedBy` and `recruitedByAffiliateId` The public apply endpoint (`POST /api/p/id/{programId}/apply`) optionally accepts a recruiter reference that establishes the one-recruiting-level link on the new affiliate row. Two fields exist: - `recruitedBy` (string): the current field. Accepts the recruiter's affiliate id as a GUID string, their per-campaign link slug, or their account-level `?via=` slug. - `recruitedByAffiliateId` (GUID): the legacy field. Still accepted for older integrations. ```bash curl -sS https://api.rekomi.com/api/p/id/0789abcd-.../apply \ -H "Content-Type: application/json" \ -d '{ "email": "newaffiliate@example.com", "fullName": "New Affiliate", "recruitedBy": "v6media" }' ``` Rules: - The reference must resolve to an `Approved` affiliate in the same campaign being applied to. Unresolvable values and mismatches are silently dropped (the application succeeds, but `RecruitedByAffiliateId` stays null). - Self-recruit attempts are silently dropped, not rejected: if the applicant is the same Clerk user as the recruiter, or applies with the same normalized email, the application still succeeds without the recruiter link. - If the campaign has `enabled = false`, recruiter binding is skipped entirely: the application succeeds but no recruiter is stored. - The fields are **not** discoverable from the public apply schema for SEO reasons; only callers who have a recruiter reference (typically from a tagged URL) should pass them. In practice you don't construct this by hand. The recruitment URL (returned by `GET /api/v1/me/sub-affiliate/link`) carries `recruited_by={token}` as a query param, where the token is the recruiter's account-level `?via=` slug when they have one, else their per-campaign link slug, else their affiliate id GUID. The public apply landing page reads that param and forwards it as `recruitedBy` in the form submission. ## MCP tools For AI-agent integrations, three tools wrap the brand-side endpoints: | Tool | Maps to | |---|---| | `get_program_sub_affiliate_config` | `GET /api/v1/programs/{id}/sub-affiliate-config` | | `set_program_sub_affiliate_config` | `PUT /api/v1/programs/{id}/sub-affiliate-config` | | `list_affiliate_sub_affiliates` | `GET /api/v1/affiliates/{id}/sub-affiliates` | All three accept either `rk_live_*` (direct API key) or `rk_mcp_*` (relayed via mcp.rekomi.com). The affiliate-side `/me/sub-affiliate/*` endpoints are Clerk-JWT-only and intentionally not exposed via MCP (the relay carries brand-org identity, not per-affiliate identity). See [MCP](/docs/developers/mcp) for the auth model. ## See also - [Sub-affiliate recruiting (brands)](/docs/brands/sub-affiliate-recruiting): concept, math, refund cascade, FAQ. - [Conversion currency](/docs/developers/conversion-currency): override rows carry the same `currency` field; the override commission is denominated in the parent sale's currency. - [Webhooks](/docs/developers/webhooks): event envelope, signature verification, retry semantics. - [API reference](/docs/developers/reference#sub-affiliate-recruiting): terse endpoint signatures alongside the rest of `/api/v1/*`. --- # Tracking script & window.Rekomi Source: https://rekomi.com/docs/developers/tracking-script > Install the Rekomi browser loader and read the referral context (affiliate + campaign) on page load, in real time. --- title: Tracking script & window.Rekomi description: Install the Rekomi browser loader and read the referral context (affiliate + campaign) on page load, in real time. --- The Rekomi tracking script (`loader.js`) does two things: 1. **Captures the click**: reads the affiliate attribution param from the URL (`?via=`, `?ref=`, `?fpr=`, etc.) into `localStorage` on your domain (plus a cookie on the tracking host, which becomes first-party once you use a custom tracking domain) and records the click. 2. **Exposes `window.Rekomi`**, a small JavaScript object so your site can read the **referral context** (the affiliate's **name** and the **campaign name**, not just a slug) on page load. This is what you use to show "Referred by …", or to capture the referring affiliate on your signup form in real time. This is the direct replacement for reading `window.Rewardful.affiliate` / `window.Rewardful.campaign` at signup. `window.Rekomi` now exposes the same `referral` / `affiliate` / `campaign` shape, see [Capture the affiliate + campaign on load](#capture-the-affiliate--campaign-on-load). ## Install Paste this on every page of your site, in ``: ```html ``` Rekomi records an email a referred visitor types into any email or text field on the pages where the tag runs (signup, login, checkout and other forms) so the sale can be credited even when your checkout passes no referral. Nothing is recorded without a referral. Remove the `data-rkmi-email-capture` attribute to turn this off; explicit `Rekomi.convert()` calls still work. The `