Tracking
Webhooks
Receive real-time notifications when users click your links, when installs are attributed, and when conversions fire. Push events to Slack, your CRM, or any analytics pipeline.
Setup
Register a webhook
Provide an HTTPS URL and the event types you want to receive. Supported events: click, attribution, and conversion.
curl -X POST https://api.riftl.ink/v1/webhooks \
-H "Authorization: Bearer rl_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourserver.com/rift-webhook",
"events": ["click", "attribution", "conversion"]
}'Response:
{
"id": "6650a1b2c3d4e5f6a7b8c9d0",
"url": "https://yourserver.com/rift-webhook",
"events": ["click", "attribution", "conversion"],
"secret": "a1b2c3d4...64-char-hex-string",
"created_at": "2026-03-24T12:00:00Z"
}secret immediately — it is only returned once at creation time. You'll use it to verify webhook signatures.List your webhooks
curl https://api.riftl.ink/v1/webhooks \
-H "Authorization: Bearer rl_live_YOUR_KEY"The list response omits the secret field for security.
Delete a webhook
curl -X DELETE https://api.riftl.ink/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer rl_live_YOUR_KEY"Event payloads
Click event
Sent when a user clicks or resolves one of your links:
{
"event": "click",
"timestamp": "2026-03-24T15:00:00Z",
"data": {
"tenant_id": "6650a1b2c3d4e5f6a7b8c9d0",
"link_id": "summer-sale",
"user_agent": "Mozilla/5.0 ...",
"referer": "https://twitter.com",
"platform": "ios",
"timestamp": "2026-03-24T15:00:00Z"
}
}Attribution event
Sent when an install is attributed to one of your links:
{
"event": "attribution",
"timestamp": "2026-03-24T15:05:00Z",
"data": {
"tenant_id": "6650a1b2c3d4e5f6a7b8c9d0",
"link_id": "summer-sale",
"install_id": "device-uuid-123",
"app_version": "1.2.0",
"timestamp": "2026-03-24T15:05:00Z"
}
}Conversion event
Sent when a conversion event is ingested via a source. Includes a stable event_id for customer-side dedup on retry — use it as the idempotency key in your handler.
{
"event": "conversion",
"timestamp": "2026-03-24T15:10:00Z",
"data": {
"event_id": "66a1b2c3d4e5f6a7b8c9d0e1",
"tenant_id": "6650a1b2c3d4e5f6a7b8c9d0",
"source_id": "66a1b2c3d4e5f6a7b8c9d0e2",
"link_id": "summer-sale",
"conversion_type": "deposit",
"user_id": "usr_abc123",
"amount_cents": 10000,
"currency": "usd",
"metadata": { "tx_hash": "0xabc..." },
"timestamp": "2026-03-24T15:10:00Z"
}
}amount_cents and currency are only present for revenue-bearing conversion types. Non-revenue events (e.g. signup, tutorial_complete) omit them.Verifying signatures
Validate the HMAC signature
Every webhook request includes an X-Rift-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
import hmac, hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}Limits & retry behavior
- Maximum 2 webhooks per tenant.
- Webhook URLs must use HTTPS.
- Failed deliveries are retried 4 times with exponential backoff (0s, 1s, 5s, 25s).
- Delivery timeout is 10 seconds per attempt.
- Delivery is fire-and-forget — it does not block the API response to the original request.
GET /v1/links/{link_id}/stats on a schedule — events are the durable source of truth inside Rift's store. The webhook is a push notification for convenience, not the canonical data path.