Reward Unlocked
Fires when a member has earned a payable amount on a promotion — the canonical signal to credit the user on your side. It's the one event most integrations only need to listen to.
When it fires
Depends on the promotion's reward shape:
- Promotions with a cumulative reward threshold (members complete N batches to earn a single larger reward): fires once per member per promotion, on the transaction that crosses the threshold.
- Promotions that pay per completion (no threshold — each completion is its own reward): fires on every completion. The completion and the reward unlock are the same moment.
In both cases, delivery goes only to webhooks that have reward_unlocked selected in their Events subscription (dashboard → Webhooks → edit → Events). Webhooks suppressed by an active fraud block receive fraud_flagged instead.
What to credit
| Promotion shape | Credit this field | Why |
|---|---|---|
| Cumulative / threshold | {{cumulative_user_payout}} | The sum of every transaction's user payout on this promotion — i.e. the full reward the member just unlocked. |
| Per-completion (no threshold) | {{user_payout}} | The dollar amount owed on this transaction. The member may complete again later and get another reward_unlocked event — each is independent. |
Use {{member_id}} (the external ID you originally signed the link with) to look up the user in your system, and store {{transaction_id}} for idempotency in case of redelivery.
Example payload
With the default body template (no custom body configured), the JSON we POST looks like this:
{
"event": "reward_unlocked",
"member_id": "abc123",
"cumulative_user_payout": "1.0000",
"user_payout": "0.0250",
"org_retention": "0.0050",
"org_gross": "0.0300",
"platform_cut": "0.0100",
"gross_revenue": "0.0400",
"points_earned": "25",
"promotion_id": "42",
"promotion_slug": "winter-promo",
"transaction_id": "1829",
"completed_at": "2026-04-21T16:01:42Z"
}
To send a minimal custom body instead, configure this body template in the webhook form:
{
"event": "{{event}}",
"user": "{{member_id}}",
"reward": "{{cumulative_user_payout}}",
"promotion": "{{promotion_slug}}",
"tx_id": "{{transaction_id}}"
}
Handler sketch
// Pseudocode — verify signature, then credit the user once per transaction.
app.post('/webhooks/rewardedmedia', (req, res) => {
if (!verifyHMAC(req.headers['x-signature'], req.rawBody, SECRET)) {
return res.status(401).end();
}
const {
event, member_id, transaction_id,
user_payout, cumulative_user_payout
} = req.body;
if (event === 'reward_unlocked' && !alreadyCredited(transaction_id)) {
// Use cumulative_user_payout for threshold promotions, user_payout for
// per-completion promotions. If you run both shapes, persist which one
// the promotion uses on your side and branch here. Simplest universal
// choice is user_payout, which is always the "dollars to credit for
// this specific transaction."
creditUser(member_id, parseFloat(user_payout));
markCredited(transaction_id);
}
res.status(200).end();
});
Full macro reference
The complete list of macros, the default body format, signing, and delivery/retry behavior all live on the Webhooks overview page.