Skip to documentation
AdQuestDocs

Webhook events

Headers, signatures, retries and the payload of each event AdQuest sends to your endpoint.

On this page

The request

AdQuest sends a POST with a JSON body to your HTTPS endpoint. Answer with any 2xx status within 10 seconds.

HeaderValue
X-AdQuest-EventThe event type, such as quest.completed.
X-AdQuest-Event-IdA stable ID for this event. Use it to ignore duplicates.
X-AdQuest-TimestampUnix time when this attempt was sent.
X-AdQuest-Signaturesha256= and the hex HMAC-SHA256 of the raw body.
X-AdQuest-Signature-V2t=<unix>,v1=<hex>. The HMAC covers the timestamp and the body, so you can reject old requests.

Verify the timestamped signature

Prefer X-AdQuest-Signature-V2. It signs the string timestamp.body with your webhook signing secret. Reject a request whose timestamp is more than 5 minutes old.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhookV2(rawBody: string, header: string, secret: string, toleranceSeconds = 300) {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header);
  if (!match) return false;
  if (Math.abs(Date.now() / 1000 - Number(match[1])) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(match[1] + "." + rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(match[2]), Buffer.from(expected));
}

Retries

  • A delivery that does not get a 2xx is retried, up to 8 attempts in all.
  • The wait grows between attempts: 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours.
  • After the last attempt the delivery is marked dead. The publisher can replay it from the delivery history in the dashboard.
  • A retried delivery keeps the same X-AdQuest-Event-Id.

Events AdQuest sends

EventWhen
quest.completedA participant’s quest completion is settled.
lottery.winnerA participant wins a lottery prize.
fulfillment.requestedA reward must be delivered by you. HTTPS endpoints only.

quest.completed

Payload
{
  "quest_id": 23,
  "platform_id": 7,
  "platform_user_id": 55,
  "external_user_id": "player-42",
  "tokens": 10,
  "completed_at": "2026-01-01T12:00:00+00:00"
}

tokens is the participant’s share. The Event-Id is quest.completed:{completion id}.

lottery.winner

Payload
{
  "lottery_id": 4,
  "claim_id": 91,
  "external_user_id": "player-42",
  "ticket_number": "1G2736",
  "award_name": "Gift card",
  "claim_expires_at": "2026-02-01T00:00:00+00:00"
}

The payload has no email or phone. To send the winner’s email, post it to the winner contact route.

fulfillment.requested

Payload for a quest reward
{
  "quest_id": 23,
  "completion_id": 301,
  "external_user_id": "player-42",
  "occurred_at": "2026-01-01T12:00:00+00:00",
  "fulfillment_id": 12,
  "idempotency_key": "fulfillment:...",
  "amount": 10,
  "asset": "ADQ_TOKEN",
  "kind": "quest_reward"
}
Payload for a lottery prize
{
  "lottery_id": 4,
  "claim_id": 91,
  "award_detail_id": 17,
  "occurred_at": "2026-01-01T12:00:00+00:00",
  "fulfillment_id": 13,
  "idempotency_key": "fulfillment:lottery_claim:91",
  "amount": 1,
  "asset": "PRIZE",
  "kind": "lottery_prize"
}

Deliver the reward, then confirm it with the acknowledge route. Repeating the same external_reference is safe.

Something unclear, missing or wrong here? Tell us