Completion
Fires every time a member finishes a batch of videos — i.e. on every successful POST /api/complete. It's the rawest "something happened" signal: one delivery per batch, regardless of whether that batch earned the member a payable reward. Most reward integrations want reward_unlocked instead — reach for completion when you need per-batch visibility (analytics, progress mirroring, engagement tracking).
When it fires
- On every non-blocked completion, for every promotion shape — both cumulative/threshold promotions and per-completion promotions.
- Fires only to webhooks that have
completionselected in their Events subscription (dashboard → Webhooks → edit → Events). - Does not fire when the completion trips a block-severity fraud rule — that transaction emits fraud_flagged instead, never
completion.
Completion vs. Reward Unlocked
These two events look similar but answer different questions. completion means "a batch was finished." reward_unlocked means "the member has earned a payable reward." On some promotion shapes those are the same moment; on others they are not.
| Promotion shape | completion fires |
reward_unlocked fires |
|---|---|---|
| Cumulative / threshold (member completes N batches to earn one larger reward) |
On every batch — N times per member. | Once, on the batch that crosses the threshold. |
| Per-completion (no threshold — each batch is its own reward) |
On every completion. | On every completion — the two coincide. |
| Fraud-blocked completion | Does not fire. | Does not fire — fraud_flagged is sent instead. |
Don't subscribe one webhook to both events on a per-completion promotion. Because completion and reward_unlocked fire on the same moment there, your endpoint would receive two callbacks per completion. Pick the one event that matches your intent, or branch on {{event}} and deduplicate on {{transaction_id}} so you only act once.
Which event should I subscribe to?
- Crediting the member a reward → use reward_unlocked. On a threshold promotion it fires exactly once, so you won't over-credit.
- Per-batch analytics, engagement metrics, or mirroring our progress meter → use
completion. You get one delivery for every batch, with that batch's{{user_payout}}and the running{{cumulative_user_payout}}. - Reversing payouts on abuse → use fraud_flagged.
Example payload
With the default body template (no custom body configured), the JSON we POST looks like this. The example below is the second of three batches on a threshold promotion — note cumulative_user_payout is still climbing toward the full reward:
{
"event": "completion",
"member_id": "abc123",
"user_payout": "0.0250",
"cumulative_user_payout": "0.0500",
"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": "1830",
"completed_at": "2026-04-21T16:06:11Z"
}
The payload shape is identical to every other event — only {{event}} changes. Each completion carries its own {{transaction_id}}; store it for idempotency in case of redelivery.
Handler sketch
// Pseudocode — verify signature, then record per-batch progress.
// Crediting the reward is reward_unlocked's job, not completion's.
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 === 'completion' && !alreadySeen(transaction_id)) {
recordBatch(member_id, transaction_id, parseFloat(user_payout));
updateProgress(member_id, parseFloat(cumulative_user_payout));
markSeen(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.