Appearance
Webhook Events
FAST dispatches webhooks for all state changes. Each webhook is signed with HMAC-SHA256 and delivered to the tenant's configured webhook URL.
Payload Structure
Every webhook follows this format:
json
{
"eventId": "uuid",
"eventType": "event.type",
"userId": "user-id",
"timestamp": "2026-01-15T10:00:00.000Z",
"data": {}
}Verification
Verify the X-Webhook-Signature header:
HMAC-SHA256(webhookSecret, requestBody) === X-Webhook-SignatureHMAC the raw body bytes as received, not a re-serialised object. Respond with a 2xx quickly; timeouts, the retry schedule and deduplication are covered in Webhook Handling.
Challenge Events
challenge.activated
A new challenge was purchased and is now active.
json
{
"data": {
"challengeId": "uuid",
"challengeTierId": "uuid",
"ownStake": 100.00,
"bonusAmount": 900.00,
"startingBalance": 1000.00,
"targetBalance": 2000.00,
"expiresAt": "2026-02-04T10:00:00.000Z"
}
}challenge.passed
User met all challenge conditions (target balance, minimum bets, minimum active days). The challenge now waits in passed_pending_choice for the user to continue or refund their own stake; see User Progress.
json
{
"data": {
"challengeId": "uuid",
"programBalance": 2000.00,
"refundableOwnStake": 100.00
}
}challenge.completed
The user chose to continue after passing. The Reward Account is created (a funded_account.activated follows).
json
{
"data": {
"challengeId": "uuid",
"finalBalance": 2000.00
}
}challenge.failed
Challenge ended without completion.
json
{
"data": {
"challengeId": "uuid",
"reason": "expired"
}
}Possible reason values: expired, max_drawdown_exceeded, insufficient_active_days.
challenge.cancelled
The user cancelled an active (or passed, not yet chosen) challenge. No refund is issued: the own stake is forfeited and the bonus voided.
json
{
"data": {
"challengeId": "uuid",
"ownStakeForfeited": 100.00,
"bonusVoided": 900.00,
"finalBalance": 870.00
}
}challenge.stake_refunded
The user exited after passing instead of continuing. Refund ownStake to the user.
json
{
"data": {
"challengeId": "uuid",
"ownStake": 100.00
}
}challenge.bonus_voided
Sent alongside a stake refund (challenge or funded phase): the bonus is void.
json
{
"data": {
"challengeId": "uuid",
"bonusAmount": 900.00
}
}challenge.expiring_soon
Challenge expires within 24 hours. Sent once per challenge.
json
{
"data": {
"challengeId": "uuid",
"hoursRemaining": 18,
"currentBalance": 85.00,
"targetBalance": 100.00,
"betsPlaced": 30,
"activeDays": 8
}
}Funded Account Events
funded_account.activated
Funded account created after the user chooses to continue. rewardsApplied is present only when rewards were consumed at creation.
json
{
"data": {
"fundedAccountId": "uuid",
"fundedAmount": 5000.00,
"challengeId": "uuid",
"profitSplitPercent": 80,
"payoutCapPercent": null,
"rewardsApplied": []
}
}funded_account.payout_requested
A payout request is waiting for the operator's decision (POST /v1/admin/payout-requests/:id/confirm or /reject). The account takes no new bets until it is decided. amount is the user's share, grossAmount what will leave the balance, operatorShare the difference. isMandatory is true when the tier's mandatory payout threshold created the request on its own; isFinal when it is the account's last allowed payout.
json
{
"data": {
"fundedAccountId": "uuid",
"payoutRequestId": "uuid",
"amount": 400.00,
"grossAmount": 500.00,
"operatorShare": 100.00,
"isFinal": false,
"isMandatory": false
}
}funded_account.payout_rejected
A pending request was rejected, by the operator or because the account closed (reason: "account_closed"). Nothing on the account changed.
json
{
"data": {
"fundedAccountId": "uuid",
"payoutRequestId": "uuid",
"amount": 400.00,
"reason": "optional text"
}
}funded_account.payout
Payout executed from a funded account. grossAmount left the balance; userPayout is the user's profit split of it and operatorShare the rest. On the first payout, the user's own stake is returned alongside the profit share (ownStakeReturned > 0). Subsequent payouts set it to 0. rewardBonus is what the user's payout_bonus_percent rewards add on top (0 if none); pay totalPayout = userPayout + ownStakeReturned + rewardBonus. All figures are before any tax, which is the operator's matter. When accountClosed is true, this was the account's last allowed payout and the account is closed. isMandatory is true for a payout created by the mandatory payout threshold.
json
{
"data": {
"fundedAccountId": "uuid",
"payoutRequestId": "uuid",
"userPayout": 400.00,
"grossAmount": 500.00,
"ownStakeReturned": 100.00,
"rewardBonus": 0,
"totalPayout": 500.00,
"operatorShare": 100.00,
"newBalance": 5350.00,
"forfeitedProfit": 0,
"accountClosed": false,
"isMandatory": false
}
}forfeitedProfit is the share a cap kept from the last payout, which can no longer be paid once the account ends; otherwise 0. A payout takes only its gross amount off the balance and the rest stays on it.
funded_account.revoked
Funded account closed by FAST, by the user, or by an admin.
json
{
"data": {
"fundedAccountId": "uuid",
"reason": "funded_drawdown_exceeded",
"finalBalance": 4400.00
}
}reason is funded_drawdown_exceeded, funded_inactivity (14 days without an accepted bet or a payout), first_payout_deadline (no payout requested within the tier's fundedFirstPayoutDays), voluntary_cancellation, or, for an admin revocation, the reason the admin gave (default manual_admin_revoke). No profit is held apart from the balance, so whatever was on it ends with the account, as finalBalance. A payout request still pending is rejected first, with a funded_account.payout_rejected.
funded_account.stake_refunded
Own stake refunded during the funded phase (tiers with fundedStakeRefundable). Refund ownStake to the user. A challenge.bonus_voided is sent with it.
json
{
"data": {
"fundedAccountId": "uuid",
"ownStake": 100.00
}
}Cooldown Events
cooldown.triggered
User hit the daily loss limit and entered a cooldown period.
json
{
"data": {
"challengeId": "uuid",
"fundedAccountId": null,
"reason": "daily_drawdown_exceeded"
}
}cooldown.lifted
Cooldown period ended. User can place bets again. Exactly one of challengeId and fundedAccountId is set.
json
{
"data": {
"challengeId": "uuid",
"fundedAccountId": null,
"reason": "cooldown_duration_expired"
}
}Bet Events
bet.validation_result
Sent for every placed bet with its validation outcome.
json
{
"data": {
"userId": "user-001",
"betId": "BET-12345",
"status": "rejected",
"reasonCodes": ["max_stake_exceeded"]
}
}betId is your externalBetId. status is accepted or rejected; see Rejection Codes.
Wagering Bonus Events
bonus.activated
A wagering bonus was activated for a user.
json
{
"data": {
"bonusId": "uuid",
"offerId": "uuid",
"offerName": "Weekend Reload",
"bonusAmount": 25.00,
"wageringTarget": 500.00,
"expiresAt": "2026-02-15T00:00:00.000Z"
}
}bonus.progress
Wagering progress updated (sent when a milestone is crossed, not every bet).
json
{
"data": {
"bonusId": "uuid",
"wageredSoFar": 250.00,
"wageringTarget": 500.00,
"progress": 0.5,
"milestone": 50
}
}bonus.completed
User fulfilled wagering requirements. Bonus reward released.
json
{
"data": {
"bonusId": "uuid",
"bonusAmount": 25.00,
"totalWagered": 510.00,
"completedAt": "2026-02-10T14:30:00.000Z"
}
}bonus.forfeited
Bonus was forfeited.
json
{
"data": {
"bonusId": "uuid",
"reason": "withdrawal"
}
}Currently sent with reason: "withdrawal", when a withdrawal forfeits the user's active bonuses.
bonus.expired
Bonus expired before completion.
json
{
"data": {
"bonusId": "uuid",
"wageredSoFar": 300.00,
"wageringTarget": 500.00,
"progress": 0.6
}
}Mission Events
mission.enrolled
User enrolled in a mission (auto-enroll or manual join).
json
{
"data": {
"missionId": "uuid",
"missionName": "Weekend Warrior",
"target": 10,
"endsAt": "2026-01-22T00:00:00.000Z"
}
}mission.progress
Mission crossed 50% of its target (sent once per mission).
json
{
"data": {
"missionId": "uuid",
"missionName": "Weekend Warrior",
"progress": 5,
"target": 10,
"percent": 50
}
}mission.completed
User completed a mission.
json
{
"data": {
"missionId": "uuid",
"missionName": "Weekend Warrior",
"rewardType": "balance_bonus",
"rewardValue": 10
}
}mission.expired
Mission expired before completion.
json
{
"data": {
"missionId": "uuid",
"missionName": "Weekend Warrior"
}
}Tournament Events
tournament.started
A tournament transitioned from upcoming to active. Tournament-wide events (started, settled, cancelled) carry an empty top-level userId.
json
{
"data": {
"tournamentId": "uuid",
"name": "Weekly High Roller"
}
}tournament.user_joined
A user joined a tournament.
json
{
"data": {
"tournamentId": "uuid",
"tournamentName": "Weekly High Roller"
}
}tournament.user_invited
A user was invited to a tournament (invite-mode).
json
{
"data": {
"tournamentId": "uuid",
"tournamentName": "Weekly High Roller",
"userId": "user-001"
}
}tournament.settled
Tournament ended and prizes distributed.
json
{
"data": {
"tournamentId": "uuid",
"name": "Weekly High Roller",
"totalEntries": 128
}
}tournament.cancelled
Tournament was cancelled by admin.
json
{
"data": {
"tournamentId": "uuid"
}
}Loyalty Events
loyalty.tier_upgraded
User promoted to a higher loyalty tier.
json
{
"data": {
"newTier": "Gold",
"newTierLevel": 3,
"currentPoints": 5200
}
}loyalty.tier_downgraded
User demoted to a lower tier (after grace period).
json
{
"data": {
"newTier": "Silver",
"currentPoints": 800
}
}loyalty.cashback_settled
Accrued cashback was settled (zeroed) and credited to user.
json
{
"data": {
"amount": 15.50
}
}Achievement and Reward Events
achievement.earned
User earned an achievement. When earned automatically:
json
{
"data": {
"achievementCode": "first_win",
"achievementName": "First Win",
"triggerEvent": "challenge_completed"
}
}When granted by an admin, data carries achievementId instead.
reward.granted
A reward was granted to the user (from achievement, rule, or manual grant).
json
{
"data": {
"rewardId": "uuid",
"rewardType": "balance_bonus",
"rewardValue": 25,
"applyMode": "auto",
"expiresAt": "2026-02-15T00:00:00.000Z",
"achievementId": "uuid"
}
}reward.applied
A reward was consumed during purchase, at funded-account creation, or applied to an active account. When applied to an active account:
json
{
"data": {
"rewardId": "uuid",
"rewardType": "balance_boost_percent",
"rewardValue": 10,
"appliedTo": "challenge-uuid",
"effect": "+10% balance boost: +5.00 EUR on challenge"
}
}When consumed at purchase or funded-account creation, data has appliedTo, context (challenge_purchase, funded_account_creation or payout), a rewards array of { rewardId, rewardType, rewardValue, effect }, and the adjusted terms (adjustedStartingBalance, purchaseDiscount, adjustedDurationDays, or adjustedProfitSplit, adjustedPayoutCap, or for payout the payoutBonus in EUR, with appliedTo the payout request id).
reward.expired
A pending reward expired before being claimed or consumed.
json
{
"data": {
"rewardId": "uuid",
"rewardType": "purchase_discount_percent"
}
}Self-Exclusion Events
user.self_excluded
User self-excluded from the platform. excludedBy is the admin's email, or api-key when recorded with a server key.
json
{
"data": {
"reason": "self_exclusion",
"excludedBy": "compliance@operator.com",
"expiresAt": "2026-07-15T00:00:00.000Z"
}
}user.self_exclusion_lifted
Self-exclusion period ended.
json
{
"data": {
"reinstatedBy": "compliance@operator.com"
}
}Event Type Reference
| Event Type | Category | Description |
|---|---|---|
challenge.activated | Challenge | New challenge purchased |
challenge.passed | Challenge | Challenge passed; user must choose continue or refund |
challenge.completed | Challenge | User continued; challenge completed and reward account created |
challenge.failed | Challenge | Challenge ended without completion |
challenge.cancelled | Challenge | User cancelled; no refund |
challenge.expiring_soon | Challenge | Expires within 24h |
challenge.stake_refunded | Challenge | User exited after passing; own stake to be refunded by the operator |
challenge.bonus_voided | Challenge | Bonus voided (on refund) |
funded_account.activated | Funded | Account created after the user continues |
funded_account.payout_requested | Funded | Payout request waiting for the operator's decision |
funded_account.payout_rejected | Funded | Pending payout request rejected |
funded_account.payout | Funded | Payout executed |
funded_account.revoked | Funded | Account ended (drawdown, inactivity, first-payout deadline, cancellation, admin) |
funded_account.stake_refunded | Funded | Own stake refunded during the reward account phase |
cooldown.triggered | Cooldown | Daily loss limit hit |
cooldown.lifted | Cooldown | Cooldown period ended |
bet.validation_result | Bet | Placed bet accepted or rejected |
bonus.activated | Bonus | Wagering bonus started |
bonus.progress | Bonus | Wagering progress updated |
bonus.completed | Bonus | Wagering requirements met |
bonus.forfeited | Bonus | Bonus cancelled/forfeited |
bonus.expired | Bonus | Bonus expired |
mission.enrolled | Mission | User enrolled in mission |
mission.progress | Mission | Mission passed 50% of target |
mission.completed | Mission | Mission completed |
mission.expired | Mission | Mission expired |
tournament.started | Tournament | Tournament went active |
tournament.user_joined | Tournament | User joined tournament |
tournament.user_invited | Tournament | User invited (invite-mode) |
tournament.settled | Tournament | Prizes distributed |
tournament.cancelled | Tournament | Tournament cancelled |
loyalty.tier_upgraded | Loyalty | User promoted |
loyalty.tier_downgraded | Loyalty | User demoted |
loyalty.cashback_settled | Loyalty | Cashback credited |
achievement.earned | Achievement | Achievement unlocked |
reward.granted | Reward | Reward given to user |
reward.applied | Reward | Reward consumed/applied |
reward.expired | Reward | Reward expired unclaimed |
user.self_excluded | Self-Exclusion | User self-excluded |
user.self_exclusion_lifted | Self-Exclusion | Exclusion lifted |