Agent skill
Rewardful Webhooks Skill
Receive and verify Rewardful webhooks. Use when setting up a Rewardful webhook endpoint, checking the X-Rewardful-Signature header (HMAC-SHA256 hex of the raw body), debugging 401 signature failures, or handling affiliate and referral events like affiliate.confirmed, referral.converted, commission.created and payout.paid.
Install this skill
npx skills add hookdeck/webhook-skills --skill rewardful-webhooks
Rewardful (affiliate and referral tracking for Stripe SaaS) sends a JSON POST to each endpoint you add on the dashboard's Webhooks page when affiliates, links, coupons, referrals, sales, commissions or payouts change. Each request carries an X-Rewardful-Signature header: the lowercase hex HMAC-SHA256 of the raw request body, keyed with that endpoint's Signing Secret.
When to Use This Skill
- How do I receive Rewardful webhooks?
- How do I verify the
X-Rewardful-Signatureheader? - How do I handle
affiliate.confirmed,referral.converted,commission.createdorpayout.paidevents? - Why does my Rewardful webhook signature verification fail (401)?
- Why does Rewardful keep retrying my webhook?
Verification (core)
Header: X-Rewardful-Signature: <64 hex chars>, no prefix. Signed content: the raw body bytes only. No timestamp, so there is no replay window to check. Key: the endpoint's Signing Secret as UTF-8 bytes, used as-is (not base64- or hex-decoded).
const crypto = require('crypto');
function verifyRewardfulSignature(rawBody, signature, secret = process.env.REWARDFUL_WEBHOOK_SECRET) {
if (!secret || !Buffer.isBuffer(rawBody)) return false;
// Validate before decoding: Buffer.from(x, 'hex') silently drops bad characters.
if (typeof signature !== 'string' || !/^[0-9a-fA-F]{64}$/.test(signature)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return crypto.timingSafeEqual(expected, Buffer.from(signature, 'hex')); // equal length: 32 bytes
}
- Sign the raw bytes (
express.raw({ type: '*/*' }),await request.arrayBuffer(),await request.body()); parsing and re-serializing the JSON breaks the signature. - Missing, malformed or mismatched signature:
401. Bad JSON after a valid signature:400. - Return
200to acknowledge. Rewardful counts only200as success; any other status is retried with exponential backoff for 3 days, then dropped. - In Python use
hmac.compare_digeston bytes (bytes.fromhexafter a[0-9a-fA-F]{64}check).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Dispatch on the body's event.type (<object>.<action>); no header names the event. The body is { object, event: { id, type, created_. Dedupe on event.id.
| Event | Sent When |
|---|---|
affiliate.created | An affiliate signed up or was created in the dashboard or API |
affiliate.confirmed | An affiliate confirmed their email address |
referral.lead | A referral became a lead |
referral.converted | A referral became a paying customer |
sale.created | A sale was created |
sale.refunded | A sale was refunded |
commission.created | A commission was created |
commission.paid | A commission was paid |
payout.due | A payout is due |
payout.paid | A payout was paid |
payout.failed | A payout failed |
Rewardful documents 31 types in total (affiliate_link.*, affiliate_coupon.*, sale.*, commission.*, payout.* and more); see references/overview.md.
Environment Variables
REWARDFUL_WEBHOOK_SECRET=your_endpoint_signing_secret # Webhooks page in the Rewardful dashboard
Local Development
Rewardful can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 rewardful --path /webhooks/rewardful
Use the printed URL as the endpoint URL in Rewardful.
Resources
- references/overview.md - What the webhooks are, all event types, payload and delivery
- references/setup.md - Add an endpoint and find the Signing Secret
- references/verification.md - Signature details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests