Leads and subscriptions
Report leads, sales after a lead and subscription renewals from your server – and which integrations report renewals by themselves.
If your program pays per lead, or you sell subscriptions, your own server can report all of it to Affilyflow with the store API with a key: leads, the sale that follows a lead, and every payment of a subscription. The rules are the same as for every other sale – cookie period, blocked affiliates, program rules, self-referral and sales held for review.
Every call needs the store's API key in Authorization: Bearer afs_…. Create it under Settings → API keys and keep it in an environment variable, e.g. AFFILYFLOW_API_KEY. Send Idempotency-Key with every POST, so a call can be retried after a timeout without counting twice – see Idempotency.
POST https://app.affilyflow.com/api/v1/store/leads
GET https://app.affilyflow.com/api/v1/store/leads/{lead_id}
POST https://app.affilyflow.com/api/v1/store/leads/{lead_id}/reject
POST https://app.affilyflow.com/api/v1/store/leads/{lead_id}/qualify
POST https://app.affilyflow.com/api/v1/store/conversionsWho reports what?
| Integration | Leads | Subscription renewals |
|---|---|---|
| Your server (API key) | Yes – POST /leads | Yes – POST /conversions with "renewal": true |
The tracking code (data-lead-url) | Yes – when the customer reaches the thank-you page | No – the browser doesn't see renewals |
| The Stripe webhook | No | Yes – every payment by itself, see Stripe |
| WooCommerce Subscriptions | No | Yes – the plugin from version 1.14.0, see WooCommerce |
| The Shopify app | No | No |
| Magento, PrestaShop, Shopware, Smartweb, DanDomain, Shoporama, Webflow | Through the tracking code | Only from your server with the API |
Leads from your server
A lead from the server works across devices and can be qualified or rejected afterwards. It is for lead programs only – a program that pays per sale answers 422 not_lead_program.
curl -X POST https://app.affilyflow.com/api/v1/store/leads \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Idempotency-Key: LEAD-2026-0042" \
-H "Content-Type: application/json" \
-d '{"lead_id":"LEAD-2026-0042","afid":"c_AbC123xYz_-9","occurred_at":"2026-11-13T09:30:00Z"}'| Field | Required | Meaning |
|---|---|---|
lead_id | Yes | Your own ID for the lead: 1–100 printable ASCII characters without spaces, e.g. a number or a UUID. Never an email address or other personal data. |
afid | When there is a click | The click ID (c_…). Alias: click_id. |
coupons | No | The customer's discount codes as a list. Without a valid click, a personal discount code attributes the lead. |
value, currency | No | A value for the lead, e.g. an estimated order value, and its currency. The commission is always the program's fixed amount per lead. |
occurred_at | Recommended | When the lead happened (ISO 8601). The cookie period is measured from it. Not in the future and at most 180 days old. |
buyer_hash, buyer_hash_n | Recommended | The customer's fingerprint, as for sales – see Buyer fingerprint. |
test | No | true = a test lead on the store's test link. Costs nothing and isn't counted. |
- One lead per
lead_id. The samelead_idagain gives200 duplicateand isn't counted twice. - At most one lead per click. Another lead on the same click gives
422 lead_per_click. If the same visitor can become several leads (e.g. several bookings), turn on Several leads per click under Program → Terms → Leads and sales. A rejected lead doesn't count. - The program's terms apply. The lead gets the program's fixed amount per lead and its approval period, and follows the same rules as a sale.
The response is 201 with status: "ok" and the lead as GET shows it (id, status, test, created_at, approval_due_at, commission, network_fee, attribution, confirmed_by_server, kind, lead_id, lead_qualified_at, awaiting_qualification and more). A lead that got 422 is not recorded – the codes are in Status codes.
Look up a lead
curl https://app.affilyflow.com/api/v1/store/leads/LEAD-2026-0042 \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"An unknown lead_id gives 404 not_found. Look up a test lead with ?test=true.
Reject an invalid lead
curl -X POST https://app.affilyflow.com/api/v1/store/leads/LEAD-2026-0042/reject \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"code":"duplicate","reason":"The same customer already asked for a quote"}'codeis one ofduplicate,test_order,other_source,invalid_traffic,not_deliveredorother(the default).otherneeds areasonof at least 3 characters.reasonis at most 300 characters, and the affiliate sees it.- Only a pending lead within the approval period can be rejected – or a lead flagged for review. The affiliate is notified.
- The response is
200withstatus: "rejected"(oralready_rejected) and the lead. An approved lead gives409 already_reviewed, a lead Affilyflow is reviewing422 held_by_affilyflow, and a lead after the approval period422 reject_window.
Leads that must be qualified
If you turn on Leads must be qualified under Program → Terms → Leads and sales, every new lead waits until you have qualified it – it is never approved automatically before that. Qualify it with the API or with Approve under Sales in the dashboard.
curl -X POST https://app.affilyflow.com/api/v1/store/leads/LEAD-2026-0042/qualify \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY"The response is 200 with status: "qualified" (or already_qualified) and the lead. A rejected lead gives 409 already_rejected. A qualified lead then follows the approval period like every other.
Leads from the website
The tracking code can also report a lead without code when the customer reaches your thank-you page (data-lead-url, see Leads without code). It reports at most one lead per visit, and the same one-lead-per-click rule applies.
Lead and sale
Under Program → Terms → Leads and sales, choose what a lead program pays for:
| Choice | The lead | A later sale |
|---|---|---|
| Leads only (default) | The program's fixed amount | No commission |
| Lead + sale | The program's fixed amount | The program's sale commission (percentage or fixed amount) |
| Sale only | Recorded without commission | The program's sale commission |
- The sale is linked to the lead through the same click – whether the sale is reported by the tracking code, a plugin, Stripe or your server – or through
lead_idonPOST /conversions. - The sale commission is locked on the click, like the program's rate.
- A sale after a lead is measured from the lead: if the sale comes within the cookie period after the lead, it earns commission.
curl -X POST https://app.affilyflow.com/api/v1/store/conversions \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Idempotency-Key: 1042" \
-H "Content-Type: application/json" \
-d '{"order":"1042","total":2400,"currency":"DKK","lead_id":"LEAD-2026-0042"}'With lead_id you don't need to send the click again. If the lead doesn't exist, the answer is 422 lead_not_found (a test lead only matches with "test": true); if it is rejected, 422 lead_rejected; and if the program pays per lead only, 422 lead_only_program.
Subscriptions from your server
If you sell subscriptions outside Stripe, your server reports every payment to POST /conversions. You choose the number of months that earn commission under Recurring commission (subscriptions) on Program (6, 12 or 24 months or lifetime), and it is locked on the click.
The first payment is an ordinary sale with the click (or the customer's discount codes) and the subscription's ID:
curl -X POST https://app.affilyflow.com/api/v1/store/conversions \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Idempotency-Key: INV-5001" \
-H "Content-Type: application/json" \
-d '{"order":"INV-5001","total":199,"currency":"DKK","afid":"c_AbC123xYz_-9","subscription_id":"SUB-881","period":"2026-11"}'Every renewal is sent with "renewal": true and the same subscription_id – without a click:
curl -X POST https://app.affilyflow.com/api/v1/store/conversions \
-H "Authorization: Bearer $AFFILYFLOW_API_KEY" \
-H "Idempotency-Key: INV-5114" \
-H "Content-Type: application/json" \
-d '{"order":"INV-5114","total":199,"currency":"DKK","subscription_id":"SUB-881","renewal":true,"period":"2026-12"}'| Field | Meaning |
|---|---|
subscription_id | Your own ID for the subscription, at most 120 characters. Never an email address. |
renewal | true on a renewal. Needs subscription_id, and a renewal can't have a lead_id. |
period | Optional: the period the payment covers, e.g. "2026-11". At most 40 characters. Two renewals for the same period with different order numbers aren't counted twice. |
- The rules are the same as for Stripe: the number of months is locked on the click, a subscription earns commission until the cap is reached, and if the first payment is rejected, the renewals earn no commission either.
- Commission is calculated from
total– the goods after discounts, excluding VAT and shipping – as for a sale. - Every renewal is its own sale with its own order number. It is refunded on its own with the refunds endpoint.
- The response to a recorded renewal is
201 okwithcycle(which payment it is) andcap(the cap from the click). - With
"test": trueall of it works as test sales, renewals included.
status | When |
|---|---|
not_recurring | The program doesn't pay commission on renewals. |
cap_reached | The commission period for this subscription is over. |
first_rejected | The subscription's first payment was rejected. |
unknown_subscription | No first payment with this subscription_id in your store. Send the first payment with subscription_id and the click first. |
duplicate_period | The subscription already has a renewal for this period (with another order number). |
subscription_exists | The subscription already has a first payment. Send the next ones with "renewal": true. |
Code snippets
The code snippets under Custom-built store in Setup and in Server-side tracking have the functions ready: in PHP affilyflow_lead(…) and affilyflow_sale(…) with the named arguments subscriptionId:, renewal:, period: and leadId:, and in JavaScript/TypeScript affilyflowLead({ leadId, click, coupons, email, value, currency, occurredAt }) and affilyflowSale({ …, subscriptionId, renewal, period, leadId }).
See also Conversion API, Status codes and Approval and refunds.
Missing something?
Write to us – we answer quickly, and the page gets fixed.