Skip to main content
ElasticFunnels can POST a small, standardized JSON payload to a URL on your affiliate’s side every time a commission-relevant event happens — independent of which gateway processed the order. This is the outbound side of affiliate tracking. The payload is intentionally lean: it contains only what an affiliate needs to reconcile a conversion in their own system. Customer details, billing addresses, and product line items are deliberately excluded. If you need rich gateway-aware payloads (with customer info, products, etc.) for your own systems, use the Merchant Postback URLs instead.

How it differs from the legacy macro postback

Affiliates have an existing single-URL GET mechanism with macros like {subid}, {amount}, {txn_id} — that one is unchanged and still works. The system documented here is additive:
Legacy postback_urlOutbound Postback URLs (this page)
MethodGET with query macrosPOST with JSON body
URLsOne per affiliateOne per event
AuthNoneHMAC X-EF-Signature (+ optional Bearer token)
RetriesBest-effort, no logQueued + exponential backoff + delivery log
EventsPurchase onlyPurchase, refund, chargeback, renewal, cancel
Affiliate typesaffcenter onlyAll affiliate types
You can use either, both, or neither.

Configuration

In Affiliates → Edit affiliate → Postback URLs you’ll find:
  1. HMAC Signing Secret — auto-generated per affiliate. Used to verify each request. Keep it secret. You can rotate it at any time; old endpoints will start failing signature checks until they pick up the new secret.
  2. Per-event Postback URLs — one row per supported event. Each row has:
    • URLhttps://example.com/webhooks/affiliate-purchase
    • Bearer Token (optional) — sent as Authorization: Bearer … if set
    • Enabled — toggle without losing the URL
  3. Send test — fires a synthetic payload (with "test": true) for that event.
  4. Recent Deliveries — log of every attempt with HTTP status, payload, response body, and a manual retry button.

Supported events

Event keyWhen it fires
purchaseA purchase conversion is approved for this affiliate.
refundAn order or order line is refunded.
chargebackA chargeback is recorded against the order.
subscription_renewalA recurring subscription is successfully billed.
subscription_renewal_failedA recurring billing attempt fails.
subscription_cancelThe subscription is cancelled or stopped.
Affiliate postbacks intentionally do not include shipped, abandon, or failed_payment — those aren’t commission-bearing events.

Request format

HTTP

POST https://your-endpoint.example.com/webhooks/affiliate-purchase
Content-Type: application/json
User-Agent: ElasticFunnels-AffiliatePostback/1.0
X-EF-Event: purchase
X-EF-Delivery-Id: 01HXYZ...
X-EF-Timestamp: 1735689600
X-EF-Signature: sha256=<hex hmac>
X-EF-Brand-Id: 42
X-EF-Affiliate-Id: 123
Authorization: Bearer <optional-token-if-configured>

Signature verification

The signature is HMAC_SHA256(secret, "<X-EF-Timestamp>.<raw_body>"), hex-encoded and prefixed with sha256=. Always verify both the signature and the timestamp (reject anything older than ~5 minutes to defeat replays).
$secret = getenv('EF_AFFILIATE_POSTBACK_SECRET');
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_EF_TIMESTAMP'] ?? '';
$received = $_SERVER['HTTP_X_EF_SIGNATURE'] ?? '';

if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    exit('Timestamp too old');
}

$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $payload, $secret);

if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit('Bad signature');
}

$event = json_decode($payload, true);
// process $event...
http_response_code(200);
echo 'ok';

Response handling

Return any 2xx response to acknowledge delivery. Anything else (timeouts, non-2xx, network errors) is recorded as a failure and retried with exponential backoff at 1m, 5m, 30m, 2h, 12h (5 attempts total) before being marked dead. Dead deliveries can be manually retried from the Recent Deliveries log.

Payload schema

Every event uses the same envelope. Numeric amounts may be negative for clawback events (refund, chargeback, subscription_renewal_failed).
{
  "event": "purchase",
  "event_id": "ef_aff_evt_01HXYZABC...",
  "occurred_at": "2026-04-18T15:32:01+00:00",
  "test": false,
  "brand_id": 42,

  "affiliate": {
    "id": 123,
    "type": "affcenter",
    "merchant_account_id": 8801,
    "default_subid": "spring_promo"
  },

  "offer": {
    "funnel_id": 17,
    "funnel_code": "vip-funnel",
    "name": "VIP Funnel"
  },

  "amount": 49.00,
  "currency": "USD",
  "commission": 12.00,

  "transaction": {
    "order_id": "GW-ORDER-1001",
    "public_order_id": "EF-1001",
    "transaction_id": "ch_3PXyz",
    "conversion_code": "cv_01HXYZ...",
    "original_conversion_code": null,
    "type": "purchase",
    "status": "approved",
    "is_subscription": false
  },

  "tracking": {
    "click_code": "ck_AbCdEf",
    "session_id": "sess_01HXY...",
    "subid": "campaign_a",
    "subid2": "creative_1",
    "subid3": null,
    "subid4": null,
    "subid5": null,
    "utm_source": "facebook",
    "utm_medium": "paid",
    "utm_campaign": "spring_2026",
    "utm_term": null,
    "utm_content": "ad_set_3",
    "gclid": null,
    "fbclid": "fb.1.1700000000.AbCdEf",
    "vtid": null
  }
}

Field reference

FieldNotes
eventOne of the supported event keys above.
event_idUnique, prefixed ef_aff_evt_…. Use it to dedupe on your side.
occurred_atISO-8601, server time of the underlying conversion event.
testtrue for “Send test” payloads, false for live events.
affiliate.*Identifies the affiliate the conversion is attributed to.
offer.nameThe funnel title — typically what you see as your “offer name”.
offer.funnel_codeStable, URL-safe slug of the funnel.
amountOrder amount in the currency below. Negative for clawbacks.
commissionThe affiliate’s calculated commission for this event.
transaction.transaction_idGateway transaction id when available.
transaction.conversion_codeElasticFunnels conversion identifier.
transaction.original_conversion_codeFor refunds/chargebacks: the original purchase’s conversion code.
tracking.subidsubid5Affiliate-defined subids captured from the click.
tracking.click_codeEF’s click identifier; correlate with your own click logs.

What’s deliberately not in the payload

  • Customer name, email, phone, address, IP — affiliates don’t need PII to reconcile.
  • Product-level details (SKUs, line items, shipping/billing).
  • Anything specific to the underlying gateway.
If you need that data for your own systems, use the Merchant Postback URLs — those are signed by the merchant secret and contain the full conversion record.

Operational details

  • Deliveries are processed asynchronously.
  • Failed deliveries are retried automatically after their backoff window has elapsed.
  • The Recent Deliveries section in the affiliate form shows delivery attempts and outcomes.
  • Rotating the signing secret immediately invalidates the old one. Coordinate the change with your affiliate before rotating in production.

Testing locally

Point a URL at webhook.site or ngrok, hit Send test for the event you want, and you’ll see the payload + headers arrive within seconds.