Track Sale

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

Last updated Oct 15, 2026

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. Required for proper recurring commission attribution.

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
  }
}