API reference
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.
| Header | Value |
|---|---|
| X-AdQuest-Event | The event type, such as quest.completed. |
| X-AdQuest-Event-Id | A stable ID for this event. Use it to ignore duplicates. |
| X-AdQuest-Timestamp | Unix time when this attempt was sent. |
| X-AdQuest-Signature | sha256= and the hex HMAC-SHA256 of the raw body. |
| X-AdQuest-Signature-V2 | t=<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
| Event | When |
|---|---|
| quest.completed | A participant’s quest completion is settled. |
| lottery.winner | A participant wins a lottery prize. |
| fulfillment.requested | A 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.