Refuel Docs

Webhooks

Webhooks let you receive real-time HTTP notifications when events happen in your Refuel account. Configure a webhook endpoint URL in your dashboard, and Refuel will send POST requests with event payloads whenever something important occurs.

Available Events

EventDescription
conversion.createdA new conversion has been recorded. Fires immediately when a conversion event is received.
conversion.approvedA conversion has been approved (either automatically by fraud checks or manually by the company).
conversion.rejectedA conversion has been rejected due to fraud flags or manual review.
affiliate.createdA new affiliate has signed up through the widget or API.
affiliate.link_createdAn affiliate has generated a new referral link.
payout.processingA payout has been initiated and is being processed via Stripe.
payout.completedA payout has been successfully transferred to the affiliate.
payout.failedA payout transfer has failed.
click.recordedA new click event has been tracked (high volume -- use sparingly).

Payload Format

Every webhook request is a POST with a JSON body following this envelope format:

Webhook envelopejson
{
  "id": "evt_abc123",
  "type": "conversion.created",
  "createdAt": "2026-03-30T14:22:00Z",
  "data": {
    // Event-specific payload
  }
}

conversion.created

conversion.created payloadjson
{
  "id": "evt_abc123",
  "type": "conversion.created",
  "createdAt": "2026-03-30T14:22:00Z",
  "data": {
    "conversionId": "conv_xyz",
    "orderId": "order_12345",
    "orderValue": 9999,
    "currency": "USD",
    "commissionAmount": 1000,
    "affiliatePayout": 800,
    "platformFee": 200,
    "status": "pending",
    "affiliateId": "aff_abc",
    "linkCode": "abc123",
    "fraudScore": 0.05
  }
}

conversion.approved

conversion.approved payloadjson
{
  "id": "evt_def456",
  "type": "conversion.approved",
  "createdAt": "2026-03-30T14:25:00Z",
  "data": {
    "conversionId": "conv_xyz",
    "orderId": "order_12345",
    "orderValue": 9999,
    "affiliatePayout": 800,
    "affiliateId": "aff_abc",
    "approvedAt": "2026-03-30T14:25:00Z"
  }
}

payout.completed

payout.completed payloadjson
{
  "id": "evt_ghi789",
  "type": "payout.completed",
  "createdAt": "2026-03-30T16:00:00Z",
  "data": {
    "payoutId": "pay_abc",
    "affiliateId": "aff_abc",
    "amount": 5000,
    "currency": "USD",
    "stripeTransferId": "tr_...",
    "paidAt": "2026-03-30T16:00:00Z"
  }
}

affiliate.created

affiliate.created payloadjson
{
  "id": "evt_jkl012",
  "type": "affiliate.created",
  "createdAt": "2026-03-30T10:00:00Z",
  "data": {
    "affiliateId": "aff_new",
    "email": "newaff@example.com",
    "companyId": "cmp_abc123",
    "linkCode": "xyz789"
  }
}

Signature Verification

Every webhook request includes a signature in the X-Refuel-Signature header. You should always verify this signature to ensure the request is authentic and has not been tampered with.

The signature is an HMAC-SHA256 hash of the raw request body, using your webhook signing secret as the key. Your signing secret is available in the dashboard under Settings → Webhooks.

How it works

  1. Refuel computes HMAC-SHA256(webhook_secret, raw_body)
  2. The hex-encoded result is sent in the X-Refuel-Signature header
  3. Your server computes the same HMAC and compares the two values
  4. If they match, the request is authentic

Verification in Node.js

verify-webhook.tstypescript
import crypto from "crypto";

function verifyWebhookSignature(
  rawBody: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Verification in Python

verify_webhook.pypython
import hmac
import hashlib

def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Example Webhook Handler

Here is a complete Express.js webhook handler that verifies the signature and processes different event types.

webhook-handler.tstypescript
import express from "express";
import crypto from "crypto";

const app = express();
const WEBHOOK_SECRET = process.env.REFUEL_WEBHOOK_SECRET!;

// Important: use raw body for signature verification
app.post(
  "/webhooks/refuel",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-refuel-signature"] as string;
    const rawBody = req.body.toString();

    // Verify signature
    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(rawBody)
      .digest("hex");

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      console.error("Invalid webhook signature");
      return res.status(401).json({ error: "Invalid signature" });
    }

    // Parse the verified payload
    const event = JSON.parse(rawBody);

    switch (event.type) {
      case "conversion.created":
        console.log("New conversion:", event.data.conversionId);
        // e.g., send internal Slack notification
        break;

      case "conversion.approved":
        console.log("Conversion approved:", event.data.conversionId);
        // e.g., update your internal order records
        break;

      case "payout.completed":
        console.log("Payout sent:", event.data.payoutId);
        // e.g., send receipt email to affiliate
        break;

      case "affiliate.created":
        console.log("New affiliate:", event.data.email);
        // e.g., add to your CRM
        break;

      default:
        console.log("Unhandled event:", event.type);
    }

    // Always respond with 200 to acknowledge receipt
    res.json({ received: true });
  }
);

app.listen(3000, () => console.log("Webhook server running on port 3000"));

Best Practices

  • 1.Always verify signatures. Never trust a webhook payload without verifying the HMAC signature first.
  • 2.Respond quickly. Return a 200 status within 5 seconds. Process heavy work asynchronously with a queue.
  • 3.Handle duplicates. Webhook delivery is at-least-once. Use the event id to deduplicate.
  • 4.Use HTTPS. Always use an HTTPS endpoint URL for your webhook receiver.
  • 5.Monitor failures. Refuel retries failed deliveries with exponential backoff (up to 3 retries over 1 hour). Check your dashboard for delivery logs.