Help Center / Dashboard & Reports / Sending results to your own systems (webhooks, postbacks & API)
Sending results to your own systems (webhooks, postbacks & API)
Have TrafficDefenseIQ call your backend or your tracker when an upload finishes scoring, or pull your data by API.
Under Settings → Integrations you can add destinations that TrafficDefenseIQ calls automatically the moment an upload finishes scoring. Anyone on your account can manage them. There are two kinds.
Webhooks — every plan
A webhook sends one JSON summary per upload to a URL you provide: the upload's ID and filename, its row count, the low/medium/high counts, and a link to the results. Each request is signed — verify it by computing an HMAC-SHA256 of "<timestamp>.<body>" with your signing secret and comparing it to the X-TDIQ-Signature header. The secret is shown once when you create the webhook, so copy it then.
Example payload — POST to your URL
{
"event": "upload.completed",
"upload": {
"id": "5b1f6e9e-2f5b-4e7a-9c7e-9b6f2b2b6a11",
"filename": "everflow-conversions-2026-01-15.csv",
"row_count": 842,
"results_url": "https://app.trafficdefenseiq.com/dashboard/reports/leads?upload=5b1f6e9e-2f5b-4e7a-9c7e-9b6f2b2b6a11"
},
"counts": {
"low": 701,
"medium": 118,
"high": 23
}
}The request also carries an X-TDIQ-Signature header like t=1758200000,v1=<hex>. Recompute the HMAC over "<t>.<raw body>" with your signing secret, compare it to v1, and reject anything with a timestamp more than a few minutes old (replay protection). Postbacks below are plain GET requests and aren't signed.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody must be the exact request body string, before JSON parsing.
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(parts.t + "." + rawBody)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && a.length === b.length && timingSafeEqual(a, b);
}Python
import hmac, hashlib, time
# raw_body must be the exact request body, before JSON parsing.
def verify(raw_body: str, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(
secret.encode(), (parts["t"] + "." + raw_body).encode(), hashlib.sha256
).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts.get("v1", ""))Postbacks — High plan
A postback calls a URL once per lead, auto-syncing your review decisions back to your tracker — the way trackers like Everflow, CAKE, and HasOffers expect. Creating a postback destination needs the High plan; Basic and Medium can still create webhook destinations above. You write the URL as a template using macros, for example https://track.example.com/postback?tid={transaction_id}&risk={risk_level}&score={score}. Available macros: transaction_id, lead_id, upload_id, risk_level, score, flagged, email, phone, ip, payout, affiliate_id, affiliate_name, offer_id, offer_name, converted_at, event. You don't have to send every lead: add conditions to a postback and only leads that pass all of them are sent. You can filter on fraud score (for example, 80 or higher), risk level, whether a phone, email, IP, or transaction ID is present, payout, a specific affiliate or offer, and whether a particular detection rule flagged the lead. Use Edit on a destination to change its conditions later.
Postback examples by tracker
Starting a postback destination from a preset below fills in a working template for that tracker automatically — this is the same template it uses.
Everflow — Replace YOUR-TRACKING-DOMAIN with your Everflow tracking domain. event_id=0 reverses only the base conversion event -- change it only if you understand that transaction_id alone reverses every event tied to that session (installs, upsells, and all). Everflow also supports an optional verification_token parameter if you've enabled one on your account.
URL template
https://YOUR-TRACKING-DOMAIN/event?status=rejected&transaction_id={transaction_id}&event_id=0Filled in for a sample High-risk lead
https://YOUR-TRACKING-DOMAIN/event?status=rejected&transaction_id=TEST-TRANSACTION-123&event_id=0
TUNE / HasOffers — Replace YOUR_NETWORK_ID and YOUR_NETWORK_TOKEN with your TUNE network's own values. The token is a real API credential embedded directly in the URL -- treat this destination as sensitive, the same as a password.
URL template
https://YOUR_NETWORK_ID.api.hasoffers.com/Apiv3/json?NetworkToken=YOUR_NETWORK_TOKEN&Target=Conversion&Method=updateStatus&transaction_id={transaction_id}&data[status]=rejectedFilled in for a sample High-risk lead
https://YOUR_NETWORK_ID.api.hasoffers.com/Apiv3/json?NetworkToken=YOUR_NETWORK_TOKEN&Target=Conversion&Method=updateStatus&transaction_id=TEST-TRANSACTION-123&data[status]=rejected
Affise (needs adjustment before use) — Affise identifies a conversion by its own clickid, which TrafficDefenseIQ doesn't capture today -- this template won't work as-is until you map that in yourself. status=3 is Affise's numeric code for Declined.
URL template
https://YOUR-AFFISE-DOMAIN/postback?clickid=YOUR_CLICK_ID&action_id={transaction_id}&status=3Filled in for a sample High-risk lead
https://YOUR-AFFISE-DOMAIN/postback?clickid=YOUR_CLICK_ID&action_id=TEST-TRANSACTION-123&status=3
Testing, retries, and activity
- Send test fires one sample call so you can confirm your endpoint works before real uploads depend on it.
- Calls are sent within moments of an upload finishing. A call counts as delivered when your server answers with a 2xx status; redirects aren't followed.
- A failed call is retried automatically — after 1 minute, then 5 minutes, 30 minutes, and 2 hours — up to 5 attempts. Open a destination's Activity to see every call and its result, and use Retry now or Resend to send one again immediately.
- Pause a destination to stop sending without deleting it.
Read-only API — High plan
Under Settings → API keys, an account owner on the High plan can create a read-only, account-scoped API key for pulling your own uploads and scored leads into your BI or CRM tools. The full key is shown once at creation — copy it then, since it isn't stored anywhere you can retrieve it again. Nothing in this API accepts writes; it only reads what's already in your account. Revoke a key any time from the same page.
Two endpoints, both GET, both authenticated with Authorization: Bearer <your key>, both paginated (page and page_size, up to 500 per page) and rate-limited to 600 requests an hour per key.
GET /api/v1/leads — filter by status, risk, or upload_id
curl "https://app.trafficdefenseiq.com/api/v1/leads?risk=high&page_size=50" \ -H "Authorization: Bearer tdiq_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"leads": [
{
"id": "8f0a1b2c-3d4e-4f56-8a7b-9c0d1e2f3a4b",
"email": "j.martinez88@gmail.com",
"phone": "+13055550142",
"affiliate_id": "20017",
"affiliate_name": "Click 2 Convert",
"offer_id": "881",
"offer_name": "Home Warranty Quote",
"ip_address": "184.51.211.9",
"transaction_id": "TX-100201",
"payout": 12.5,
"converted_at": "2026-09-10T14:04:41.000Z",
"status": "active",
"review_state": "unreviewed",
"fraud_verdict": null,
"upload_id": "5b1f6e9e-2f5b-4e7a-9c7e-9b6f2b2b6a11",
"score": 92,
"risk_level": "high",
"rule_results": {
"duplicate_detection": {
"score": 100,
"flagged": true,
"reason": "Same email or phone appears 4 times in this upload."
}
}
}
],
"page": 1,
"page_size": 50,
"total": 23
}GET /api/v1/uploads
curl "https://app.trafficdefenseiq.com/api/v1/uploads?page_size=20" \ -H "Authorization: Bearer tdiq_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Response
{
"uploads": [
{
"id": "5b1f6e9e-2f5b-4e7a-9c7e-9b6f2b2b6a11",
"filename": "everflow-conversions-2026-01-15.csv",
"status": "completed",
"row_count": 842,
"flagged_count": 141,
"created_at": "2026-01-15T09:02:11.000Z",
"completed_at": "2026-01-15T09:04:37.000Z"
}
],
"page": 1,
"page_size": 20,
"total": 37
}More in Dashboard & Reports
Didn't answer it? Contact us.