AffiliatePipe

Partner API / Live stream

Live stream

WSSwss://api.affiliatepipe.com:2053/v1/stream

One open WebSocket that pushes your clicks, filtered clicks and sales the moment they happen. For live dashboards, bots and alerts, without polling.

Addresswss://api.affiliatepipe.com:2053/v1/stream
KeyYour API key: in the Authorization: Bearer header, or as the first message
Eventsclick, click.filtered, conversion.created, conversion.approved, conversion.paid, conversion.clawed_back
DelayAbout 2 seconds
Catch upsince replays up to 200 events from the last 24 hours
Limits3 open streams per key. Not counted against your 1,000 calls a day

Parameters

eventsstring, optionalComma separated event types to receive. Default: all of them.
sinceISO 8601 time, optionalReplay events after this time first (24 hours back at most, 200 events), each marked "replay": true. Use the time of the last event you saw to fill a gap after reconnecting.

Both go in the address (?events=click,conversion.created&since=...) or in the auth message. Never put the key in the address: it is refused.

Connect

From a server, send the key as a header. A browser cannot set headers, so it sends {"type":"auth","key":"..."} as its first message, within 10 seconds. Keep the key on your server where you can: anyone who has it can read your figures.

const ws = new WebSocket("wss://api.affiliatepipe.com:2053/v1/stream");
ws.onopen = () => ws.send(JSON.stringify({ type: "auth", key: process.env.AP_KEY }));
ws.onmessage = (e) => {
  const ev = JSON.parse(e.data);
  if (ev.type === "conversion.created") console.log("Sale on", ev.data.domain, ev.data.commission / 100);
};
ws.onclose = (e) => setTimeout(connect, 3000);   // reconnect with since= to catch up
import asyncio, json, os, websockets   # pip install websockets

async def main():
    url = "wss://api.affiliatepipe.com:2053/v1/stream?events=click,conversion.created"
    async with websockets.connect(url, additional_headers={"Authorization": "Bearer " + os.environ["AP_KEY"]}) as ws:
        async for msg in ws:
            ev = json.loads(msg)
            print(ev["type"], ev.get("data", {}).get("domain"))

asyncio.run(main())
curl -i -N --http1.1 \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  -H "Authorization: Bearer $AP_KEY" \
  https://api.affiliatepipe.com:2053/v1/stream

Messages

Every message is one JSON object with a type. After the key is accepted you get ready, then events as they happen. Replayed events carry "replay": true.

{"type": "ready", "events": ["click", "click.filtered", "conversion.created", "conversion.approved", "conversion.paid", "conversion.clawed_back"], "replayed": 0, "ping_seconds": 25}

Every event

conversion.createdA sale through your link is recorded. Commission is pending for 45 days.
conversion.approvedThe 45 days have passed with no refund. The commission counts towards your next payout.
conversion.paidThe commission was included in a payout to your Stripe account.
conversion.clawed_backThe sale was refunded or disputed within 120 days, so its commission is reversed.
clickSomeone opened your link and the click was counted.
click.filteredA click on your link was filtered and will never earn. reason says why, for example self_referral, bot, duplicate or embedded, as in the public filtered-click log.

conversion.created

{
    "type": "conversion.created",
    "data": {
        "id": 412,
        "domain": "greyhoundapi.com",
        "amount": 4900,
        "commission": 490,
        "currency": "usd",
        "status": "pending",
        "recurring": true,
        "created_at": "2026-10-20T09:21:11Z"
    }
}

conversion.approved

{
    "type": "conversion.approved",
    "data": {
        "id": 412,
        "domain": "greyhoundapi.com",
        "amount": 4900,
        "commission": 490,
        "currency": "usd",
        "status": "approved",
        "recurring": true,
        "created_at": "2026-10-20T09:21:11Z"
    }
}

conversion.paid

{
    "type": "conversion.paid",
    "data": {
        "id": 412,
        "domain": "greyhoundapi.com",
        "amount": 4900,
        "commission": 490,
        "currency": "usd",
        "status": "paid",
        "recurring": true,
        "created_at": "2026-10-20T09:21:11Z"
    }
}

conversion.clawed_back

{
    "type": "conversion.clawed_back",
    "data": {
        "id": 412,
        "domain": "greyhoundapi.com",
        "amount": 4900,
        "commission": 490,
        "currency": "usd",
        "status": "clawed_back",
        "recurring": true,
        "created_at": "2026-10-20T09:21:11Z"
    }
}

click

{
    "type": "click",
    "data": {
        "id": "clk_9f2a41c07be3d855e1a0",
        "domain": "greyhoundapi.com",
        "country": "GB",
        "device": "desktop",
        "referrer_host": "reddit.com",
        "utm_source": "reddit",
        "utm_medium": "social",
        "utm_campaign": "october",
        "created_at": "2026-10-20T09:14:02Z"
    }
}

click.filtered

{
    "type": "click.filtered",
    "data": {
        "id": "clk_4b70e2a19c38f6d02e5b",
        "domain": "greyhoundapi.com",
        "country": "US",
        "device": "bot",
        "referrer_host": null,
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "created_at": "2026-10-20T09:14:40Z",
        "reason": "bot"
    }
}

conversion.* data is the same as the webhook events. Money is in cents.

Staying connected

  • We send a WebSocket ping every 25 seconds; any client library answers it. You can also send {"type":"ping"} and get pong.
  • The stream restarts about once an hour and closes with code 1012. Reconnect after a few seconds with since set to your last event's time, and nothing is missed.
  • Close codes: 4001 wrong, missing or revoked key, or a paused account; 4029 more than 3 streams on one key.
  • For sales you must never miss, use webhooks as well: they are retried for a day if you are offline.