Webhooks

Receive real-time notifications when clicks happen. Webhooks let you know the expected affiliate tag before the user even arrives at your site.

Setup

  1. Go to your Merchant Dashboard
  2. Add your webhook URL (must be HTTPS)
  3. Copy your webhook secret for signature verification
  4. Start receiving events!

Event: click.created

Sent when a user clicks a Linkbay-protected link pointing to your site.

Payload

{
  "event": "click.created",
  "click_id": "xyz789",
  "expected_tag": "CREATOR123",
  "creator_id": "abc123",
  "timestamp": "2026-01-15T10:30:00Z",
  "referrer": "youtube.com/watch?v=abc123",
  "geo": {
    "country": "US",
    "city": "New York"
  }
}

Payload Fields

FieldTypeDescription
eventstringThe event type (always click.created)
click_idstringUnique identifier for this click
expected_tagstringThe affiliate tag that should receive attribution
creator_idstringThe Linkbay ID of the creator
timestampstringISO 8601 timestamp of the click
referrerstring?Where the link was clicked (if available)
geoobject?Geographic information about the clicker

Headers

Each webhook request includes these headers:

HeaderDescription
X-Linkbay-SignatureHMAC-SHA256 signature for payload verification
X-Linkbay-EventEvent type (e.g., click.created)
X-Linkbay-TimestampUnix timestamp of when the webhook was sent
Content-Typeapplication/json

Verifying Signatures

Always verify webhook signatures to ensure the request came from Linkbay.

JavaScript/TypeScript

import crypto from 'crypto';

function verifySignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler
export async function POST(request: Request) {
  const signature = request.headers.get('X-Linkbay-Signature');
  const payload = await request.text();

  if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return Response.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const data = JSON.parse(payload);
  // Process the webhook...

  return Response.json({ received: true });
}

Python

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Handling Webhooks

Example: Store for Later Verification

// Store the expected tag when webhook arrives
const clickStore = new Map();

export async function POST(request: Request) {
  const signature = request.headers.get('X-Linkbay-Signature');
  const payload = await request.text();

  // Verify signature...

  const data = JSON.parse(payload);

  // Store the expected tag indexed by click_id
  clickStore.set(data.click_id, {
    expectedTag: data.expected_tag,
    creatorId: data.creator_id,
    timestamp: data.timestamp,
  });

  return Response.json({ received: true });
}

// Later, at checkout:
function verifyAtCheckout(clickId: string, arrivedTag: string) {
  const stored = clickStore.get(clickId);
  if (!stored) {
    // Fallback to API lookup
    return lookupClickViaAPI(clickId);
  }

  return {
    isHijacked: stored.expectedTag !== arrivedTag,
    expectedTag: stored.expectedTag,
  };
}

Best Practices

  • Respond quickly. Return a 200 status within 5 seconds. Process webhooks asynchronously if needed.
  • Always verify signatures. Never trust unverified webhook payloads.
  • Handle duplicates. Webhooks may occasionally be sent multiple times. Use click_id for deduplication.
  • Use the API as fallback. If a webhook is missed, you can always use the Lookup Click API.
  • Store click data. Keep webhook data for comparison at checkout time.

Retry Policy

If your endpoint returns an error (non-2xx status), we'll retry:

  • First retry: 30 seconds after initial failure
  • Second retry: 5 minutes after first retry
  • Third retry: 30 minutes after second retry

After 3 failed attempts, the webhook is marked as failed. You can view failed webhooks in your dashboard.