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
- Go to your Merchant Dashboard
- Add your webhook URL (must be HTTPS)
- Copy your webhook secret for signature verification
- 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
| Field | Type | Description |
|---|---|---|
event | string | The event type (always click.created) |
click_id | string | Unique identifier for this click |
expected_tag | string | The affiliate tag that should receive attribution |
creator_id | string | The Linkbay ID of the creator |
timestamp | string | ISO 8601 timestamp of the click |
referrer | string? | Where the link was clicked (if available) |
geo | object? | Geographic information about the clicker |
Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
X-Linkbay-Signature | HMAC-SHA256 signature for payload verification |
X-Linkbay-Event | Event type (e.g., click.created) |
X-Linkbay-Timestamp | Unix timestamp of when the webhook was sent |
Content-Type | application/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_idfor 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.