Leads and subscriptions

Report leads, sales after a lead and subscription renewals from your server – and which integrations report renewals by themselves.

View as Markdown

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.

EndpointsHTTP
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/conversions

Who reports what?

IntegrationLeadsSubscription renewals
Your server (API key)Yes – POST /leadsYes – POST /conversions with "renewal": true
The tracking code (data-lead-url)Yes – when the customer reaches the thank-you pageNo – the browser doesn't see renewals
The Stripe webhookNoYes – every payment by itself, see Stripe
WooCommerce SubscriptionsNoYes – the plugin from version 1.14.0, see WooCommerce
The Shopify appNoNo
Magento, PrestaShop, Shopware, Smartweb, DanDomain, Shoporama, WebflowThrough the tracking codeOnly 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.

POST /api/v1/store/leadsShell
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"}'
FieldRequiredMeaning
lead_idYesYour 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.
afidWhen there is a clickThe click ID (c_…). Alias: click_id.
couponsNoThe customer's discount codes as a list. Without a valid click, a personal discount code attributes the lead.
value, currencyNoA 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_atRecommendedWhen 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_nRecommendedThe customer's fingerprint, as for sales – see Buyer fingerprint.
testNotrue = a test lead on the store's test link. Costs nothing and isn't counted.
  • One lead per lead_id. The same lead_id again gives 200 duplicate and 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

GET /api/v1/store/leads/{lead_id}Shell
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

POST /api/v1/store/leads/{lead_id}/rejectShell
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"}'
  • code is one of duplicate, test_order, other_source, invalid_traffic, not_delivered or other (the default). other needs a reason of at least 3 characters. reason is 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 200 with status: "rejected" (or already_rejected) and the lead. An approved lead gives 409 already_reviewed, a lead Affilyflow is reviewing 422 held_by_affilyflow, and a lead after the approval period 422 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.

POST /api/v1/store/leads/{lead_id}/qualifyShell
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:

ChoiceThe leadA later sale
Leads only (default)The program's fixed amountNo commission
Lead + saleThe program's fixed amountThe program's sale commission (percentage or fixed amount)
Sale onlyRecorded without commissionThe 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_id on POST /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.
The sale after a leadShell
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:

First paymentShell
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:

RenewalShell
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"}'
FieldMeaning
subscription_idYour own ID for the subscription, at most 120 characters. Never an email address.
renewaltrue on a renewal. Needs subscription_id, and a renewal can't have a lead_id.
periodOptional: 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 ok with cycle (which payment it is) and cap (the cap from the click).
  • With "test": true all of it works as test sales, renewals included.
statusWhen
not_recurringThe program doesn't pay commission on renewals.
cap_reachedThe commission period for this subscription is over.
first_rejectedThe subscription's first payment was rejected.
unknown_subscriptionNo first payment with this subscription_id in your store. Send the first payment with subscription_id and the click first.
duplicate_periodThe subscription already has a renewal for this period (with another order number).
subscription_existsThe 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.

Contact support