Skip to content
Browse documentation

Webhooks

Receive signed JSON events for conversions, offers, partner sign-ups and access requests, and verify them.

Last updated 3 min read

On this page

Webhooks push events from your workspace to your own systems — a CRM, a data warehouse, Zapier — as signed JSON POST requests. Unlike partner postbacks (one URL per partner, with macros), a webhook receives a fixed JSON document for every event it subscribes to.

Events#

EventFires whendata
conversion.createdA new non-duplicate conversion is stored — any status, including rejected.The conversion
conversion.updatedStatus, revenue, payout or sale amount of a conversion changes (postback, panel, API, import, hold release).The conversion + previous
offer.createdAn offer is added.The offer
offer.updatedAny offer setting changes.The offer + changed (list of fields)
affiliate.signupA partner applies or is added.The partner
affiliate.approvedA partner becomes active.The partner
access.requestedA partner asks to run an offer.Offer, partner, status
access.decidedAn access request is approved, rejected or blocked.Offer, partner, status + previous_status

Events are queued in the same database transaction as the change, so a committed change is never missed.

Create a webhook#

  1. Go to Automation → Webhooks (/app/automation/webhooks).
  2. Click New webhook.
  3. Enter a Name and the Endpoint URL — a public http(s) URL without credentials, IP address or internal host name.
  4. Tick the Events.
  5. Keep Active checked and click Save webhook.

A workspace can have up to 50 webhooks. Open a webhook to see its URL, the Signing secret (admins only), the delivery log, Edit, Rotate secret and Delete.

Request format#

URL
POST /hooks/conversions HTTP/1.1
Content-Type: application/json
User-Agent: ArcTrack-Webhook/1.0
X-ArcTrack-Event: conversion.created
X-ArcTrack-Delivery: 81723
X-ArcTrack-Timestamp: 1760262000
X-ArcTrack-Signature: t=1760262000,v1=5f2b…c9

The ArcTrack-Webhook/1.0 user agent and the X-ArcTrack-… header names are fixed technical identifiers of the tracking engine — the same on every installation, whatever the brand.

JSON
{
  "id": "1b7e8c2a-4f0d-4e57-9a3b-0c6d2e1f8a90",
  "event": "conversion.created",
  "workspace_id": 1,
  "created_at": "2026-10-12T09:40:00.000Z",
  "data": {
    "id": "5f0c2a8e-1d3b-4c6e-9f7a-2b8d4e6f1a3c", "click_id": "0192f3c1a2b4e1f0c9d84b2a7e6f5d3c",
    "offer_id": 12, "affiliate_id": 7, "advertiser_id": 3, "event": "sale", "status": "approved",
    "status_reason": "", "revenue": "10.0000", "payout": "6.0000", "sale_amount": "49.9000", "currency": "USD",
    "adv_txid": "ORDER-1001", "method": "s2s", "conv_ts": "2026-10-12T09:40:00.000Z",
    "click_ts": "2026-10-12T09:12:44.120Z", "sub1": "summer", "sub2": "", "sub3": "", "sub4": "", "sub5": "",
    "source_id": "", "gclid": "", "country": "US", "device_type": "mobile"
  }
}

id is unique per event — use it to ignore repeats. Money is a 4-decimal string; times are ISO-8601 UTC. conversion.updated adds "previous": {"status", "revenue", "payout", "sale_amount"}. Test events carry "test": true.

Verify the signature#

The X-ArcTrack-Signature header is t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Verify it on the raw body and reject timestamps older than a few minutes:

JavaScript
// Node.js
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // replay window
  const mac = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(parts.v1));
}
PYTHON
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    mac = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts["v1"])

Rotate secret generates a new secret; deliveries are signed with it immediately.

Delivery and retries#

RuleValue
SuccessHTTP 2xx within 10 seconds
RedirectsNot followed — a 3xx answer is a failed attempt
Retries1 min, 5 min, 30 min, 2 h, 6 h (6 attempts over about 8.5 hours), then failed
DestinationsPublic addresses only
Paused webhookQueues nothing new; its pending deliveries fail with webhook paused

Deliveries can arrive out of order and, after a retry, more than once: process them idempotently by id.

Test and retry#

  • On the webhook page, under Send a test event, choose an event and click Send test event. A delivery with sample data appears in the log within seconds (marked Test).
  • In the delivery log, select rows and use Retry to queue them again.

Webhooks or partner postbacks?#

NeedUse
A partner's tracker must learn about that partner's conversions, in its own URL formatPartner postbacks
Your own system needs every conversion and status change as JSON, signedWebhooks
You need offer, partner or access-request changesWebhooks
Something unclear or missing in this article?Tell us