Tracking API

Track Sale

Record an attributed sale and create the partner commission from your backend

View as Markdown

Call this endpoint from your server after a payment succeeds to record the conversion and trigger commission creation.

Endpoint

POST https://api.affitor.com/api/v1/track/sale
Authorization: Bearer YOUR_PROGRAM_API_KEY
Content-Type: application/json

Authentication

All requests require a program API key sent as a Bearer token in the Authorization header. The API key is found in your program settings in the Affitor dashboard.

Authorization: Bearer YOUR_PROGRAM_API_KEY

Request Fields

transaction_idstringRequired

Your unique payment identifier. Used for duplicate detection — re-sending the same value returns 409.

amount_centsintegerRequired

Sale amount in the smallest currency unit (e.g. cents for USD). Must be a positive integer.

tax_centsintegerOptional

The tax included in amount_cents, in the same unit. Accepted but not used yet: commissions are calculated on amount_cents as sent.

customer_keystringConditional

Your internal customer/user ID, set during the signup tracking step. Used to resolve attribution. At least one of customer_key or click_id must match an existing customer record.

click_idstringConditional

The Affitor click ID (affitor_click_id cookie value). Used as fallback attribution when customer_key is not provided.

currencystringOptional

USD, EUR, or VND. Defaults to USD. Another code returns 422 unsupported_currency and creates no sale, commission or fee. Amounts are not converted between currencies: commissions and platform fees are calculated on the amount as sent, and invoices are issued in USD.

sale_typestringOptional

"payment" (default) or "subscription".

paymentsubscription
line_itemsobjectOptional

Arbitrary line-item detail to store with the sale record.

is_recurringbooleanOptional

true if this is a recurring billing charge. Defaults to false.

subscription_idstringOptional

Your subscription identifier, stored with the sale. Affitor does not use it to find the customer or to calculate the commission: send each renewal as its own sale, with its own transaction_id.

subscription_intervalstringOptional

"monthly", "quarterly", or "annual".

monthlyquarterlyannual
product_idstringOptional

Your product identifier. Used for per-product commission policy matching.

occurred_atstringOptional

When the payment happened, as ISO 8601 with an offset (for example 2026-10-16T09:30:00Z), no more than 30 days old and at most 5 minutes in the future. Stored for reporting only: commissions, attribution windows and hold periods still use the time Affitor receives the call.

providerstringOptional

Your payment provider. Stored for reporting.

stripepolardodopaddlelemonsqueezycreemsepaypayosapipayx402other
provider_payment_idstringOptional

The provider's own payment id (1 to 255 characters). Stored for reporting; duplicate detection still uses transaction_id.

livemodebooleanOptional

false records a test sale, the same as additional_data.test_mode: true. true or omitted does not force a live sale: additional_data.test_mode: true or a test API key still records a test sale.


Request Body Example

curl -X POST https://api.affitor.com/api/v1/track/sale \
-H "Authorization: Bearer YOUR_PROGRAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "transaction_id": "txn_abc123",
  "customer_key": "usr_9876",
  "amount_cents": 4900,
  "currency": "USD",
  "sale_type": "payment"
}'

Subscription example

{
  "transaction_id": "inv_sub_001",
  "customer_key": "usr_9876",
  "amount_cents": 4900,
  "currency": "USD",
  "sale_type": "subscription",
  "is_recurring": true,
  "subscription_id": "sub_stripe_xyz",
  "subscription_interval": "monthly",
  "product_id": "prod_pro_plan"
}

Response

{
"success": true,
"sale_id": 42,
"commission_id": 17,
"message": "Sale tracked successfully"
}
FieldTypeDescription
successbooleantrue on success.
sale_idinteger or nullID of the created sale event record; null when the attribution window expired.
commission_idinteger or nullID of the created commission record; null when the attribution window expired.
messagestringHuman-readable confirmation.
Idempotency

Re-sending the same transaction_id returns 409 Conflict with error.code duplicate_transaction and the first sale's id in error.details.sale_id. Affitor does not create a duplicate sale, so you can safely retry on network failure.

409 is not an error

A 409 response means the sale was already recorded successfully. Do not retry a 409 — treat it as a successful prior call and continue your payment flow normally.


Error Responses

Most errors use the platform envelope { "data": null, "error": { "status", "name", "message", "details", "code" } }. A retired API key and rate limiting use the typed envelope { "error": { "code", "message", ... } }. Either way, branch on error.code; the message text may change.

Statuserror.codeWhen
400invalid_requesttransaction_id missing or not a string; amount_cents not a positive integer; an invalid occurred_at, provider, provider_payment_id or livemode
400no_attributionNeither customer_key nor click_id resolved a customer, the customer belongs to another program, or no partner relationship was found
401unauthorizedMissing or malformed Authorization header, or the API key does not match any active program
401api_key_rotation_requiredA retired API key (the body also carries hint and docs_url)
409duplicate_transactiontransaction_id already recorded; error.details.sale_id is the recorded sale
422unsupported_currencycurrency is not USD, EUR or VND; no sale, commission or fee is created
429rate_limitedToo many requests; wait retry_after_seconds
500internal_errorSale or commission creation failed internally

Test Mode

Send additional_data.test_mode: true (or livemode: false) to create a test sale event without affecting production data. The API key is still required but transaction_id and amount_cents are optional in test mode.

{
  "additional_data": {
    "test_mode": true
  }
}

Test mode response:

{
  "success": true,
  "message": "Test sale event tracked successfully",
  "data": {
    "eventId": 12,
    "programId": 3,
    "test_mode": true
  }
}

Share feedback

Tell us what is missing or unclear on “Track Sale”.

Last updated Oct 17, 2026

On this page

Share feedback

Tell us what is missing or unclear on “Track Sale”.