Refgrow
Back to blog

How to Set Up Webhook Notifications for Affiliate Events

Alex Belogubov
How to Set Up Webhook Notifications for Affiliate Events

Webhooks are the backbone of modern SaaS integrations. Instead of polling an API every few minutes to check for changes, webhooks push data to your systems the instant something happens. For affiliate programs, this means you can trigger workflows, update dashboards, and notify your team in real time when an affiliate signs up, a referral converts, or a payout is processed.

In this guide, we'll walk through everything you need to know about setting up webhook notifications for affiliate events in Refgrow. We'll cover the available event types, how to configure endpoints, payload structures with real examples, practical use cases, Node.js code for receiving webhooks, and security best practices including HMAC signature verification.

What Are Webhooks and Why Do They Matter?

A webhook is an HTTP POST request that your application receives when a specific event occurs in another system. Think of it as a reverse API: instead of your code asking "did anything happen?" repeatedly, the external system tells your code "this just happened" the moment it occurs.

For affiliate programs, webhooks enable:

  • Real-time sync between your affiliate platform and internal systems
  • Automated workflows that trigger instantly when events occur
  • Reduced API usage — no need for constant polling
  • Better data consistency — your systems update within seconds, not minutes or hours

Available Webhook Events in Refgrow

Refgrow supports four webhook event types that cover the complete affiliate lifecycle:

Event Trigger Common Use Cases
affiliate_signup A new affiliate registers for your program Add to CRM, send welcome sequence, notify sales team
new_referral An affiliate generates a new referral (click/signup) Update internal dashboard, trigger lead scoring, notify affiliate manager
conversion A referred user completes a paid conversion Update revenue dashboards, trigger Slack celebration, sync to accounting
payout A payout is processed for an affiliate Update accounting system, send confirmation, log for compliance

Each event delivers a JSON payload containing all the relevant data for that event. Let's look at the exact payloads you'll receive.

Webhook Payload Examples

affiliate_signup

Sent when a new affiliate registers for your program through the widget or a direct invitation link.

{
  "event": "affiliate_signup",
  "timestamp": "2026-03-17T14:30:00.000Z",
  "project_id": "proj_abc123",
  "data": {
    "affiliate_id": "aff_7x9k2m",
    "email": "partner@example.com",
    "name": "Jane Smith",
    "referral_code": "JANESMITH",
    "coupon_code": "JANE20",
    "status": "active",
    "commission_rate": 25,
    "commission_type": "percentage",
    "created_at": "2026-03-17T14:30:00.000Z",
    "custom_fields": {
      "website": "https://janesmith.com",
      "social_media": "@janesmith"
    }
  }
}

new_referral

Sent when a visitor clicks an affiliate's referral link and is tracked as a new referral.

{
  "event": "new_referral",
  "timestamp": "2026-03-17T15:45:00.000Z",
  "project_id": "proj_abc123",
  "data": {
    "referral_id": "ref_m3n8p2",
    "affiliate_id": "aff_7x9k2m",
    "affiliate_email": "partner@example.com",
    "affiliate_name": "Jane Smith",
    "referral_code": "JANESMITH",
    "referred_email": "newuser@company.com",
    "referred_name": "Bob Johnson",
    "source": "referral_link",
    "landing_page": "https://yourapp.com/?ref=JANESMITH",
    "ip_country": "US",
    "created_at": "2026-03-17T15:45:00.000Z"
  }
}

conversion

Sent when a referred user completes a paid transaction (subscription, one-time purchase, or upgrade).

{
  "event": "conversion",
  "timestamp": "2026-03-17T16:20:00.000Z",
  "project_id": "proj_abc123",
  "data": {
    "conversion_id": "conv_q4r7s1",
    "referral_id": "ref_m3n8p2",
    "affiliate_id": "aff_7x9k2m",
    "affiliate_email": "partner@example.com",
    "affiliate_name": "Jane Smith",
    "customer_email": "newuser@company.com",
    "amount": 9900,
    "currency": "USD",
    "commission_amount": 2475,
    "commission_type": "percentage",
    "commission_rate": 25,
    "is_recurring": true,
    "payment_provider": "stripe",
    "provider_transaction_id": "pi_3abc123def456",
    "product_name": "Pro Plan",
    "status": "approved",
    "hold_until": "2026-04-17T16:20:00.000Z",
    "created_at": "2026-03-17T16:20:00.000Z"
  }
}

Note: The amount and commission_amount fields are in cents (smallest currency unit). Divide by 100 for the dollar amount. The hold_until field indicates when the commission becomes payable — if this date is in the future, the commission is still in its hold period.

payout

Sent when a payout is successfully processed for an affiliate.

{
  "event": "payout",
  "timestamp": "2026-03-17T18:00:00.000Z",
  "project_id": "proj_abc123",
  "data": {
    "payout_id": "pay_t5u8v3",
    "affiliate_id": "aff_7x9k2m",
    "affiliate_email": "partner@example.com",
    "affiliate_name": "Jane Smith",
    "amount": 12375,
    "currency": "USD",
    "method": "paypal",
    "paypal_email": "jane@paypal.me",
    "status": "completed",
    "conversions_included": 5,
    "period_start": "2026-02-01T00:00:00.000Z",
    "period_end": "2026-02-28T23:59:59.000Z",
    "created_at": "2026-03-17T18:00:00.000Z"
  }
}

Configuring Webhooks in Refgrow

Setting up webhooks in Refgrow takes less than a minute. Here's how:

Step 1: Navigate to Webhook Settings

In your Refgrow dashboard, go to your project settings and find the Webhooks section. You can also access it directly at https://refgrow.com/projects/[your-project]/settings/webhooks.

Step 2: Add a Webhook Endpoint

Click "Add Webhook" and enter your endpoint URL. This must be a publicly accessible HTTPS URL that can receive POST requests. For example: https://yourapp.com/webhooks/refgrow.

Step 3: Select Events

Choose which events should be sent to this endpoint. You can subscribe to all four events or select specific ones. You can create multiple webhook endpoints if you want different events routed to different systems.

Step 4: Copy Your Webhook Secret

Refgrow generates a unique webhook secret for each endpoint. Copy this secret — you'll need it to verify webhook signatures. Store it securely as an environment variable. Never hard-code secrets in your source code.

Step 5: Test the Webhook

Click "Send Test" to send a test payload to your endpoint. Verify that your server receives and processes it correctly before relying on it for production workflows.

Receiving Webhooks: Node.js Implementation

Let's build a complete webhook receiver in Node.js. We'll use Express for the HTTP server and implement proper signature verification, error handling, and event routing.

Basic Setup

const express = require('express');
const crypto = require('crypto');

const app = express();

// IMPORTANT: Use raw body for webhook routes (needed for HMAC verification)
// Parse the raw body before JSON parsing
app.post('/webhooks/refgrow',
  express.raw({ type: 'application/json' }),
  handleRefgrowWebhook
);

const WEBHOOK_SECRET = process.env.REFGROW_WEBHOOK_SECRET;

function handleRefgrowWebhook(req, res) {
  // Step 1: Verify the webhook signature
  const signature = req.headers['x-refgrow-signature'];
  const timestamp = req.headers['x-refgrow-timestamp'];

  if (!signature || !timestamp) {
    console.error('Missing webhook signature or timestamp');
    return res.status(401).json({ error: 'Missing signature' });
  }

  // Prevent replay attacks: reject webhooks older than 5 minutes
  const currentTime = Math.floor(Date.now() / 1000);
  const webhookTime = parseInt(timestamp, 10);
  if (Math.abs(currentTime - webhookTime) > 300) {
    console.error('Webhook timestamp too old');
    return res.status(401).json({ error: 'Timestamp expired' });
  }

  // Compute expected signature
  const payload = req.body; // raw Buffer when using express.raw()
  const signedContent = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(signedContent)
    .digest('hex');

  // Constant-time comparison to prevent timing attacks
  const sigBuffer = Buffer.from(signature);
  const expectedBuffer = Buffer.from(expectedSignature);

  if (sigBuffer.length !== expectedBuffer.length ||
      !crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
    console.error('Invalid webhook signature');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Step 2: Parse the verified payload
  let event;
  try {
    event = JSON.parse(payload);
  } catch (err) {
    console.error('Invalid JSON payload:', err.message);
    return res.status(400).json({ error: 'Invalid JSON' });
  }

  // Step 3: Acknowledge receipt immediately (respond within 5 seconds)
  res.status(200).json({ received: true });

  // Step 4: Process the event asynchronously
  processEvent(event).catch(err => {
    console.error(`Error processing ${event.event} event:`, err);
  });
}

async function processEvent(event) {
  switch (event.event) {
    case 'affiliate_signup':
      await handleAffiliateSignup(event.data);
      break;
    case 'new_referral':
      await handleNewReferral(event.data);
      break;
    case 'conversion':
      await handleConversion(event.data);
      break;
    case 'payout':
      await handlePayout(event.data);
      break;
    default:
      console.warn('Unknown event type:', event.event);
  }
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Let's break down what this code does:

  1. Raw body parsing: We use express.raw() instead of express.json() because we need the raw request body for HMAC signature verification. If the body is parsed to JSON first, the signature won't match.
  2. Signature verification: We compute an HMAC-SHA256 hash of the timestamp + payload using the webhook secret, then compare it with the signature sent in the header.
  3. Replay protection: We reject webhooks with timestamps older than 5 minutes to prevent replay attacks.
  4. Immediate response: We return a 200 status immediately, then process the event asynchronously. This prevents timeouts if your event processing takes a while.

Use Case 1: Sync New Affiliates with Your CRM

When a new affiliate signs up, you probably want to add them to your CRM so your partnerships team can track the relationship. Here's how to handle the affiliate_signup event with HubSpot as an example:

const axios = require('axios');

async function handleAffiliateSignup(data) {
  // Create or update contact in HubSpot
  try {
    await axios.post(
      'https://api.hubapi.com/crm/v3/objects/contacts',
      {
        properties: {
          email: data.email,
          firstname: data.name.split(' ')[0],
          lastname: data.name.split(' ').slice(1).join(' '),
          affiliate_id: data.affiliate_id,
          referral_code: data.referral_code,
          affiliate_status: data.status,
          commission_rate: `${data.commission_rate}%`,
          lifecyclestage: 'partner'
        }
      },
      {
        headers: {
          'Authorization': `Bearer ${process.env.HUBSPOT_API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );
    console.log(`Added affiliate ${data.email} to HubSpot`);
  } catch (err) {
    // Handle duplicate contact (409 = already exists)
    if (err.response?.status === 409) {
      console.log(`Affiliate ${data.email} already exists in HubSpot, updating...`);
      // Update existing contact instead
      await updateHubSpotContact(data.email, data);
    } else {
      throw err;
    }
  }
}

You can adapt this pattern for any CRM: Salesforce, Pipedrive, Close, or even a custom database. The key is mapping the affiliate data fields to your CRM's contact properties.

Use Case 2: Send Slack Notifications for Conversions

Nothing motivates a team like seeing revenue come in. Post a message to your Slack channel every time an affiliate drives a conversion:

async function handleConversion(data) {
  const amount = (data.amount / 100).toFixed(2);
  const commission = (data.commission_amount / 100).toFixed(2);
  const recurring = data.is_recurring ? ' (recurring)' : ' (one-time)';

  const message = {
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: '🎉 New Affiliate Conversion!'
        }
      },
      {
        type: 'section',
        fields: [
          {
            type: 'mrkdwn',
            text: `*Affiliate:*\n${data.affiliate_name}`
          },
          {
            type: 'mrkdwn',
            text: `*Product:*\n${data.product_name}`
          },
          {
            type: 'mrkdwn',
            text: `*Revenue:*\n$${amount} ${data.currency}${recurring}`
          },
          {
            type: 'mrkdwn',
            text: `*Commission:*\n$${commission} ${data.currency}`
          }
        ]
      },
      {
        type: 'context',
        elements: [
          {
            type: 'mrkdwn',
            text: `Via ${data.payment_provider} | Conversion ID: ${data.conversion_id}`
          }
        ]
      }
    ]
  };

  await axios.post(process.env.SLACK_WEBHOOK_URL, message);
  console.log(`Slack notification sent for conversion ${data.conversion_id}`);
}

This sends a rich formatted message to your Slack channel with all the key details at a glance. You can customize the formatting, add buttons, or route different events to different channels.

Use Case 3: Update an Internal Revenue Dashboard

If you maintain an internal dashboard or data warehouse, webhooks let you keep affiliate data in sync in real time:

const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function handleConversion(data) {
  // Insert conversion into your analytics database
  await pool.query(
    `INSERT INTO affiliate_conversions
      (conversion_id, affiliate_id, affiliate_name, customer_email,
       amount_cents, commission_cents, currency, product_name,
       payment_provider, is_recurring, status, converted_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
     ON CONFLICT (conversion_id) DO UPDATE SET
       status = EXCLUDED.status,
       amount_cents = EXCLUDED.amount_cents,
       commission_cents = EXCLUDED.commission_cents`,
    [
      data.conversion_id,
      data.affiliate_id,
      data.affiliate_name,
      data.customer_email,
      data.amount,
      data.commission_amount,
      data.currency,
      data.product_name,
      data.payment_provider,
      data.is_recurring,
      data.status,
      data.created_at
    ]
  );

  // Update affiliate summary stats
  await pool.query(
    `INSERT INTO affiliate_stats (affiliate_id, total_revenue_cents, total_conversions)
     VALUES ($1, $2, 1)
     ON CONFLICT (affiliate_id) DO UPDATE SET
       total_revenue_cents = affiliate_stats.total_revenue_cents + $2,
       total_conversions = affiliate_stats.total_conversions + 1`,
    [data.affiliate_id, data.amount]
  );

  console.log(`Dashboard updated for conversion ${data.conversion_id}`);
}

Using ON CONFLICT with upsert logic makes your handler idempotent — if the same webhook is delivered twice (which can happen in rare network conditions), your data stays consistent.

Use Case 4: Send Custom Transactional Emails

While Refgrow handles standard affiliate notifications, you might want to send custom emails that match your brand or include additional context:

const { Resend } = require('resend');
const resend = new Resend(process.env.RESEND_API_KEY);

async function handlePayout(data) {
  const amount = (data.amount / 100).toFixed(2);

  await resend.emails.send({
    from: 'partnerships@yourapp.com',
    to: data.affiliate_email,
    subject: `Your $${amount} payout has been processed`,
    html: `
      
      

Hi ${data.affiliate_name},

Great news! We've just processed your affiliate payout.

Amount $${amount} ${data.currency}
Method ${data.method}
Period ${new Date(data.period_start).toLocaleDateString()} - ${new Date(data.period_end).toLocaleDateString()}
Conversions ${data.conversions_included}

Thank you for being an amazing partner!

— The YourApp Team

` }); console.log(`Payout confirmation email sent to ${data.affiliate_email}`); }

Security: HMAC Signature Verification Deep Dive

Webhook security is not optional. Without signature verification, anyone who discovers your webhook URL could send fake events to your system, potentially triggering unauthorized payouts, corrupting your data, or exploiting your business logic.

How Refgrow Signs Webhooks

Every webhook request from Refgrow includes two security headers:

  • x-refgrow-signature — HMAC-SHA256 hash of the signed content
  • x-refgrow-timestamp — Unix timestamp (seconds) when the webhook was sent

The signed content is constructed as: {timestamp}.{raw_request_body}

The signature is computed as: HMAC-SHA256(webhook_secret, signed_content)

Why Include the Timestamp?

Including the timestamp in the signed content prevents replay attacks. Without it, an attacker who intercepts a valid webhook could replay it indefinitely. With the timestamp included:

  1. The signature is only valid for the specific timestamp
  2. Your server rejects webhooks with timestamps older than 5 minutes
  3. An attacker can't modify the timestamp without invalidating the signature

Why Use Constant-Time Comparison?

The standard === comparison operator in JavaScript (and most languages) compares strings byte-by-byte and returns false as soon as it finds a mismatch. This creates a timing side-channel: an attacker can measure how long the comparison takes to determine how many bytes of their forged signature match the real one.

Using crypto.timingSafeEqual() ensures the comparison always takes the same amount of time regardless of where the mismatch occurs, closing this attack vector.

Verification in Other Languages

If your backend isn't Node.js, here's how to verify webhooks in Python and Go:

Python

import hmac
import hashlib
import time

def verify_webhook(payload_body, signature, timestamp, secret):
    # Check timestamp freshness
    current_time = int(time.time())
    if abs(current_time - int(timestamp)) > 300:
        return False

    # Compute expected signature
    signed_content = f"{timestamp}.{payload_body}"
    expected = hmac.new(
        secret.encode('utf-8'),
        signed_content.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(signature, expected)

Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "math"
    "strconv"
    "time"
)

func verifyWebhook(payload, signature, timestamp, secret string) bool {
    // Check timestamp freshness
    ts, err := strconv.ParseInt(timestamp, 10, 64)
    if err != nil {
        return false
    }
    if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
        return false
    }

    // Compute expected signature
    signedContent := fmt.Sprintf("%s.%s", timestamp, payload)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(signedContent))
    expected := hex.EncodeToString(mac.Sum(nil))

    // Constant-time comparison
    return hmac.Equal([]byte(signature), []byte(expected))
}

Error Handling and Retry Logic

Webhooks can fail for many reasons: your server is down, a deployment is in progress, a database is temporarily unavailable. Refgrow implements automatic retries to handle transient failures:

Refgrow's Retry Policy

  • Retry attempts: Up to 5 retries after the initial delivery
  • Retry schedule: 30 seconds, 5 minutes, 30 minutes, 2 hours, 24 hours
  • Success criteria: Any 2xx HTTP status code is considered successful
  • Failure criteria: 4xx errors (except 429) are not retried; 5xx and timeouts trigger retries

Best Practices for Your Webhook Handler

  1. Respond quickly. Return a 200 status within 5 seconds. If your processing takes longer, acknowledge receipt first and process asynchronously (as shown in our code example above).
  2. Be idempotent. Your handler should produce the same result whether a webhook is delivered once or multiple times. Use the event's unique ID (conversion_id, affiliate_id, etc.) as a deduplication key.
  3. Return appropriate status codes. Return 200 for success, 401 for signature verification failure, 400 for malformed payloads. Don't return 500 for expected validation errors — that triggers unnecessary retries.
  4. Log everything. Log all incoming webhooks with their event type, ID, and processing result. This is invaluable for debugging integration issues.

Here's an idempotency pattern using a simple deduplication table:

async function processEventWithDedup(event) {
  const eventKey = `${event.event}_${event.data.conversion_id ||
    event.data.affiliate_id || event.data.referral_id ||
    event.data.payout_id}`;

  // Check if we've already processed this event
  const { rows } = await pool.query(
    'SELECT 1 FROM webhook_events WHERE event_key = $1',
    [eventKey]
  );

  if (rows.length > 0) {
    console.log(`Duplicate event skipped: ${eventKey}`);
    return;
  }

  // Process the event
  await processEvent(event);

  // Mark as processed
  await pool.query(
    'INSERT INTO webhook_events (event_key, processed_at) VALUES ($1, NOW())',
    [eventKey]
  );
}

Testing Webhooks During Development

Testing webhooks locally requires exposing your development server to the internet. Here are the recommended approaches:

# Install ngrok
npm install -g ngrok

# Start your local server
node server.js

# In another terminal, expose port 3000
ngrok http 3000

# ngrok gives you a public URL like:
# https://abc123.ngrok.io
# Use this as your webhook URL in Refgrow settings

Option 2: Refgrow's Test Webhook Feature

Use the "Send Test" button in your webhook settings to send sample payloads to your endpoint. This is the fastest way to verify your handler works correctly without waiting for real events.

Option 3: Local Replay with curl

For rapid iteration, save a webhook payload to a file and replay it locally:

# Save a test payload
cat > test-webhook.json << 'EOF'
{
  "event": "conversion",
  "timestamp": "2026-03-17T16:20:00.000Z",
  "project_id": "proj_abc123",
  "data": {
    "conversion_id": "conv_test123",
    "affiliate_id": "aff_7x9k2m",
    "affiliate_name": "Jane Smith",
    "amount": 9900,
    "currency": "USD",
    "commission_amount": 2475,
    "status": "approved"
  }
}
EOF

# Send it to your local server (skip signature verification in dev mode)
curl -X POST http://localhost:3000/webhooks/refgrow \
  -H "Content-Type: application/json" \
  -d @test-webhook.json

Note: For local testing with curl, you'll need to temporarily disable signature verification or compute a valid signature locally. Never disable signature verification in production.

Advanced: Webhook Event Chaining

Real power comes from combining multiple webhook events into workflows. Here's an example that tracks the full affiliate journey:

// Track the complete affiliate lifecycle
async function handleAffiliateSignup(data) {
  // 1. Add to CRM
  await addToCRM(data);
  // 2. Start onboarding email sequence
  await triggerEmailSequence(data.email, 'affiliate_onboarding');
  // 3. Notify partnerships team
  await notifySlack(`New affiliate signup: ${data.name} (${data.email})`);
}

async function handleNewReferral(data) {
  // 1. Update CRM with referral activity
  await updateCRMActivity(data.affiliate_id, 'referral_generated');
  // 2. If this is the affiliate's first referral, send encouragement
  const referralCount = await getReferralCount(data.affiliate_id);
  if (referralCount === 1) {
    await sendEmail(data.affiliate_email,
      'first_referral_congrats',
      { referral_name: data.referred_name }
    );
  }
}

async function handleConversion(data) {
  // 1. Update revenue dashboards
  await updateDashboard(data);
  // 2. Celebrate in Slack
  await celebrateInSlack(data);
  // 3. Check for milestone achievements
  const totalRevenue = await getAffiliateRevenue(data.affiliate_id);
  if (totalRevenue >= 100000) { // $1,000+
    await promoteAffiliate(data.affiliate_id, 'gold');
    await sendEmail(data.affiliate_email, 'gold_tier_achieved');
  }
}

async function handlePayout(data) {
  // 1. Log to accounting system
  await logToAccounting(data);
  // 2. Send branded confirmation email
  await sendPayoutConfirmation(data);
  // 3. Request review/testimonial after 3rd payout
  const payoutCount = await getPayoutCount(data.affiliate_id);
  if (payoutCount === 3) {
    await sendEmail(data.affiliate_email, 'testimonial_request');
  }
}

This kind of automated lifecycle management turns your affiliate program from a passive revenue channel into an actively managed partnership engine — all triggered by webhook events.

Monitoring and Debugging

Once your webhooks are live, you need visibility into their health. Here are key metrics to monitor:

  • Delivery success rate: Track the percentage of webhooks that receive a 200 response on the first attempt. A healthy webhook endpoint maintains 99%+ success.
  • Average response time: Monitor how long your handler takes to respond. If response times creep above 3-4 seconds, you risk timeouts.
  • Retry frequency: A sudden increase in retries indicates a problem with your endpoint — investigate immediately.
  • Event processing errors: Log and alert on any errors that occur during async event processing (after the 200 response).

Refgrow provides a webhook delivery log in your project dashboard where you can see the delivery status, response code, and response time for each webhook sent. This is your first stop when debugging integration issues.

Summary

Webhooks are the most efficient way to keep your systems synchronized with your affiliate program. With Refgrow's webhook support, you can build real-time integrations that sync affiliate data to your CRM, celebrate conversions in Slack, update internal dashboards, send custom transactional emails, and automate your entire affiliate lifecycle.

The key takeaways:

  • Always verify signatures. Never trust a webhook payload without HMAC verification.
  • Respond fast, process async. Acknowledge receipt within 5 seconds, then handle business logic in the background.
  • Build idempotent handlers. Assume every webhook might be delivered more than once.
  • Monitor delivery health. Track success rates, response times, and retry frequencies.
  • Start simple, then chain. Begin with one integration (like Slack notifications), then expand to full lifecycle automation.

Build Real-Time Affiliate Integrations

Start your 14-day free trial of Refgrow and connect your affiliate program to your entire tech stack with webhooks. No credit card required.

Start Free Trial

More from the blog

Ready to launch your affiliate program?

14-day free trial · No credit card required

Start Free Trial
Set Up Affiliate Webhook Notifications (Dev Guide)