Webhook API
Receive real-time notifications when conversions, approvals, and other events occur
Webhook API
Receive real-time notifications from refVenue when important events occur in your affiliate program.
What are Webhooks?
Webhooks are automated messages sent from refVenue to your server when specific events happen:
- New conversion tracked
- Conversion approved/rejected
- Refund processed
- Affiliate joins program
- Commission payment made
Instead of polling our API, webhooks push data to you in real-time.
Use Cases
When to use webhooks:
- Sync conversion data to your CRM
- Trigger custom notifications
- Update your internal analytics
- Automate affiliate onboarding
- Track commission payments
When NOT to use webhooks:
- Simple conversion tracking (use JavaScript or server-side API instead)
- One-way tracking only (webhooks are for receiving data)
Setup Guide
Step 1: Create a Webhook Endpoint
Create an endpoint on your server to receive webhook events:
// Express.js example
app.post('/webhooks/refvenue', async (req, res) => {
const event = req.body;
// Process the event
console.log('Received event:', event.type);
// Return 200 to acknowledge receipt
res.status(200).send('OK');
});Requirements:
- Accepts POST requests
- Returns 200 status code within 5 seconds
- Publicly accessible URL (not localhost)
- HTTPS recommended for production
Step 2: Register Your Webhook URL
- Log in to refVenue dashboard
- Go to Settings → Webhooks
- Click Add Webhook Endpoint
- Enter your webhook URL (e.g.,
https://yourdomain.com/webhooks/refvenue) - Select events to receive
- Save
Step 3: Verify Webhook Signature (Recommended)
Verify that webhook requests actually come from refVenue:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/refvenue', (req, res) => {
const signature = req.headers['x-refvenue-signature'];
const webhookSecret = process.env.REFVENUE_WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).send('Invalid signature');
}
// Process event...
res.status(200).send('OK');
});Get your webhook secret from Settings → Webhooks → Signing Secret.
Webhook Events
conversion.created
Triggered when a new conversion is tracked.
{
"type": "conversion.created",
"data": {
"id": "conv_abc123",
"program_id": "prog_xyz789",
"affiliate_id": "aff_def456",
"affiliate_code": "JOHN123",
"customer_email": "customer@example.com",
"conversion_type": "purchase",
"revenue_amount": 99.99,
"commission_amount": 9.99,
"status": "pending",
"created_at": "2024-01-15T10:30:00Z"
}
}conversion.approved
Triggered when you approve a conversion.
{
"type": "conversion.approved",
"data": {
"id": "conv_abc123",
"program_id": "prog_xyz789",
"affiliate_id": "aff_def456",
"commission_amount": 9.99,
"approved_at": "2024-01-16T14:20:00Z"
}
}conversion.rejected
Triggered when you reject a conversion.
{
"type": "conversion.rejected",
"data": {
"id": "conv_abc123",
"program_id": "prog_xyz789",
"affiliate_id": "aff_def456",
"reason": "Fraud detected",
"rejected_at": "2024-01-16T14:20:00Z"
}
}conversion.refunded
Triggered when a purchase is refunded.
{
"type": "conversion.refunded",
"data": {
"id": "conv_abc123",
"program_id": "prog_xyz789",
"affiliate_id": "aff_def456",
"original_amount": 99.99,
"refund_amount": 99.99,
"commission_reversed": 9.99,
"refunded_at": "2024-01-20T09:15:00Z"
}
}affiliate.joined
Triggered when an affiliate joins your program.
{
"type": "affiliate.joined",
"data": {
"id": "aff_def456",
"program_id": "prog_xyz789",
"name": "John Doe",
"email": "john@example.com",
"referral_code": "JOHN123",
"status": "active",
"joined_at": "2024-01-10T08:00:00Z"
}
}payout.processed
Triggered when you process commission payouts.
{
"type": "payout.processed",
"data": {
"id": "payout_ghi789",
"program_id": "prog_xyz789",
"affiliate_id": "aff_def456",
"amount": 150.00,
"currency": "USD",
"period_start": "2024-01-01T00:00:00Z",
"period_end": "2024-01-31T23:59:59Z",
"processed_at": "2024-02-01T10:00:00Z"
}
}Implementation Examples
Node.js / Express
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.REFVENUE_WEBHOOK_SECRET;
app.post('/webhooks/refvenue', (req, res) => {
// Verify signature
const signature = req.headers['x-refvenue-signature'];
const expectedSig = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expectedSig) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
// Handle different event types
switch (event.type) {
case 'conversion.created':
handleNewConversion(event.data);
break;
case 'conversion.approved':
handleConversionApproved(event.data);
break;
case 'affiliate.joined':
handleNewAffiliate(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
res.status(200).send('OK');
});
function handleNewConversion(data) {
console.log('New conversion:', data.id);
// Send to CRM, update analytics, etc.
}
function handleConversionApproved(data) {
console.log('Conversion approved:', data.id);
// Notify affiliate, update dashboard, etc.
}
function handleNewAffiliate(data) {
console.log('New affiliate:', data.email);
// Send welcome email, add to mailing list, etc.
}
app.listen(3000);Python / Flask
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['REFVENUE_WEBHOOK_SECRET']
def verify_signature(payload, signature):
expected = hmac.new(
WEBHOOK_SECRET.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhooks/refvenue', methods=['POST'])
def webhook():
# Verify signature
signature = request.headers.get('X-Refvenue-Signature')
if not verify_signature(request.json, signature):
return 'Invalid signature', 401
event = request.json
# Handle events
if event['type'] == 'conversion.created':
handle_new_conversion(event['data'])
elif event['type'] == 'conversion.approved':
handle_conversion_approved(event['data'])
elif event['type'] == 'affiliate.joined':
handle_new_affiliate(event['data'])
return 'OK', 200
def handle_new_conversion(data):
print(f"New conversion: {data['id']}")
# Your logic here
def handle_conversion_approved(data):
print(f"Conversion approved: {data['id']}")
# Your logic here
def handle_new_affiliate(data):
print(f"New affiliate: {data['email']}")
# Your logic here
if __name__ == '__main__':
app.run(port=3000)PHP
<?php
$webhookSecret = getenv('REFVENUE_WEBHOOK_SECRET');
// Get raw POST body
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
// Verify signature
$signature = $_SERVER['HTTP_X_REFVENUE_SIGNATURE'];
$expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);
if (!hash_equals($signature, $expectedSignature)) {
http_response_code(401);
exit('Invalid signature');
}
// Handle event
switch ($data['type']) {
case 'conversion.created':
handleNewConversion($data['data']);
break;
case 'conversion.approved':
handleConversionApproved($data['data']);
break;
case 'affiliate.joined':
handleNewAffiliate($data['data']);
break;
}
http_response_code(200);
echo 'OK';
function handleNewConversion($data) {
error_log("New conversion: " . $data['id']);
// Your logic here
}
function handleConversionApproved($data) {
error_log("Conversion approved: " . $data['id']);
// Your logic here
}
function handleNewAffiliate($data) {
error_log("New affiliate: " . $data['email']);
// Your logic here
}
?>Best Practices
1. Return 200 Quickly
Process events asynchronously to avoid timeouts:
app.post('/webhooks/refvenue', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
processWebhook(req.body).catch(console.error);
});2. Handle Duplicate Events
Webhooks may be sent multiple times. Make operations idempotent:
async function handleNewConversion(data) {
// Check if already processed
const exists = await db.conversions.findOne({ id: data.id });
if (exists) {
console.log('Already processed:', data.id);
return;
}
// Process and store
await db.conversions.create(data);
}3. Verify Signatures
Always verify webhook signatures in production:
if (process.env.NODE_ENV === 'production') {
if (!verifySignature(req.body, req.headers['x-refvenue-signature'])) {
return res.status(401).send('Invalid signature');
}
}4. Log Events
Keep a record of all webhook events:
app.post('/webhooks/refvenue', async (req, res) => {
// Log the raw webhook
await db.webhookLogs.create({
type: req.body.type,
payload: req.body,
received_at: new Date()
});
// Process...
res.status(200).send('OK');
});5. Handle Failures Gracefully
Don't let webhook failures break your application:
async function processWebhook(event) {
try {
await handleEvent(event);
} catch (error) {
console.error('Webhook processing failed:', error);
// Log to monitoring service
// Don't throw - already sent 200
}
}Testing Webhooks
Local Development
Use ngrok or similar to expose localhost:
# Install ngrok
npm install -g ngrok
# Expose port 3000
ngrok http 3000
# Use the ngrok URL in refVenue webhook settings
# https://abc123.ngrok.io/webhooks/refvenueTest Events
Send test events from the refVenue dashboard:
- Go to Settings → Webhooks
- Click on your webhook endpoint
- Click Send Test Event
- Select event type
- Check your server logs
Manual Testing
Use curl to test your endpoint:
curl -X POST https://yourdomain.com/webhooks/refvenue \
-H "Content-Type: application/json" \
-H "X-Refvenue-Signature: test_signature" \
-d '{
"type": "conversion.created",
"data": {
"id": "conv_test123",
"revenue_amount": 100.00,
"commission_amount": 10.00
}
}'Troubleshooting
Webhooks Not Received
Check:
- Endpoint is publicly accessible (not localhost)
- Endpoint returns 200 status code
- No firewall blocking requests
- Webhook is enabled in settings
- Check webhook delivery logs in dashboard
Signature Verification Fails
Ensure:
- Using correct webhook secret
- Verifying raw body (not parsed JSON)
- Using same hashing algorithm (SHA-256)
- Comparing signatures with timing-safe function
Timeout Errors
Solutions:
- Return 200 immediately
- Process events asynchronously
- Optimize database queries
- Increase server timeout limits
Duplicate Events
This is normal:
- refVenue retries failed deliveries
- Network issues can cause duplicates
- Make handlers idempotent (safe to run multiple times)
Webhook Delivery
Retry Policy
If your endpoint fails (non-200 response or timeout):
- Immediate retry after 1 second
- Retry after 5 seconds
- Retry after 30 seconds
- Retry after 2 minutes
- Retry after 15 minutes
- Stop after 5 failed attempts
Timeout
Webhooks timeout after 5 seconds. Return 200 faster than that.
Ordering
Events are not guaranteed to arrive in order. Use timestamps to determine sequence.
Security
HTTPS Only (Production)
Always use HTTPS endpoints in production:
- ✅
https://yourdomain.com/webhooks - ❌
http://yourdomain.com/webhooks
Verify Signatures
Don't trust webhook data without signature verification:
// ❌ Bad - accepts any POST request
app.post('/webhooks/refvenue', (req, res) => {
handleEvent(req.body); // Could be from anyone!
res.send('OK');
});
// ✅ Good - verifies signature
app.post('/webhooks/refvenue', (req, res) => {
if (!verifySignature(req.body, req.headers['x-refvenue-signature'])) {
return res.status(401).send('Invalid');
}
handleEvent(req.body);
res.send('OK');
});IP Allowlisting (Optional)
For extra security, only accept requests from refVenue IPs:
const REFVENUE_IPS = [
'203.0.113.0/24',
'198.51.100.0/24'
];
app.post('/webhooks/refvenue', (req, res) => {
const clientIP = req.ip;
if (!isIPAllowed(clientIP, REFVENUE_IPS)) {
return res.status(403).send('Forbidden');
}
// Process webhook...
});Contact support for current IP ranges.
Alternative Integration Methods
Webhooks are just one way to integrate:
- JavaScript Tracking - Client-side tracking
- Server-Side API - Direct API calls
- Stripe Integration - Automatic Stripe tracking