Webhooks — Reward Unlocked

event = reward_unlocked

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.