Built for startups,
scaled for unicorns
Successfully submitted!
Error! Please try again

GrowSurf handles referral tracking and determines when participants earn rewards β but the actual reward delivery is where your business logic lives. Custom reward fulfillment via webhooks lets you deliver any type of reward: account credits, feature unlocks, gift cards, physical merchandise, subscription upgrades, or anything else your creativity allows.
This guide covers building a webhook-based reward fulfillment system that processes GrowSurf reward events, validates eligibility, delivers the reward through your chosen method, confirms delivery back to the participant, and handles failures gracefully. You'll build a system that's reliable enough to handle your most valuable customers' rewards.
Map out what happens when a participant earns a reward, including validation, delivery, and confirmation.
Create an endpoint that processes GrowSurf's PARTICIPANT_REACHED_REWARD event.
PARTICIPANT_REACHED_REWARD event specificallyBuild the actual reward delivery logic for each reward type.
Maintain a database of all reward fulfillments for auditing and customer support.
reward_fulfillments table with columns: participant_id, reward_type, status, delivered_at, detailsNotify participants that their reward has been successfully delivered.
Build robust error handling for when reward delivery fails.
// Custom Reward Fulfillment Handler
const db = require('./database');
const giftCardService = require('./gift-card-service');
const emailService = require('./email-service');
// Reward fulfillment functions by type
const rewardFulfillers = {
account_credit: async (participant, amount) => {
const user = await db.users.findByEmail(participant.email);
if (!user) throw new Error('User not found');
await db.users.addCredit(user.id, amount);
return { type: 'credit', amount, userId: user.id };
},
feature_unlock: async (participant, featureName) => {
const user = await db.users.findByEmail(participant.email);
await db.features.unlock(user.id, featureName);
return { type: 'feature', feature: featureName, userId: user.id };
},
gift_card: async (participant, amount) => {
const card = await giftCardService.create({
amount: amount,
recipientEmail: participant.email,
recipientName: participant.firstName
});
return { type: 'gift_card', cardId: card.id, amount };
}
};
// Main reward webhook handler
app.post('/webhooks/growsurf/reward', async (req, res) => {
const { event, participant } = req.body;
if (event !== 'PARTICIPANT_REACHED_REWARD') {
return res.status(200).json({ skipped: true });
}
// Idempotency check
const existing = await db.fulfillments.findByParticipant(participant.id);
if (existing && existing.status === 'delivered') {
return res.status(200).json({ message: 'Already fulfilled' });
}
res.status(200).json({ received: true }); // Respond quickly
try {
// Determine reward type from campaign configuration
const rewardConfig = { type: 'account_credit', amount: 1000 }; // $10
const fulfiller = rewardFulfillers[rewardConfig.type];
const result = await fulfiller(participant, rewardConfig.amount);
// Log fulfillment
await db.fulfillments.create({
participantId: participant.id,
rewardType: rewardConfig.type,
status: 'delivered',
details: result,
deliveredAt: new Date()
});
// Send confirmation email
await emailService.send(participant.email, 'reward-confirmation', {
name: participant.firstName,
rewardDetails: result
});
} catch (error) {
await db.fulfillments.create({
participantId: participant.id,
status: 'failed',
error: error.message
});
console.error('Reward fulfillment failed:', error);
}
});Never send a reward confirmation email before the reward is actually delivered. If the delivery fails after sending confirmation, the participant has a bad experience and your support team has a headache. Confirm only after successful delivery.
Create an admin interface that lets your team manually fulfill, reverse, or re-process rewards. Automated systems fail sometimes, and having a manual fallback prevents participant frustration and reduces support load.
Track total reward costs (credits issued, gift cards sent, features unlocked) in real time. Set up alerts when daily or monthly reward costs exceed thresholds. This catches both program success (great!) and potential fraud (bad!) early.
GrowSurf retries failed webhooks with exponential backoff. As long as your server comes back up within the retry window, the reward event will be processed. For extra safety, implement a periodic job that queries GrowSurf's API for unfulfilled rewards and processes any that were missed.
Yes. Use GrowSurf's reward tiers to define different reward levels (e.g., first referral gets a credit, fifth referral gets a gift card). In your webhook handler, check the participant's referral count or reward tier to determine which fulfillment function to call.
For physical rewards, your fulfillment handler should create an order in your shipping/fulfillment system (e.g., ShipStation, Shopify Fulfillment) and send the participant a confirmation email with estimated delivery dates. Track the shipping status and send updates as the order progresses.
Trusted by marketing and product teams at fast-growing B2C, fintech, and SaaS companies
