RiftRift

Tracking

Conversions

Measure post-install events — signups, deposits, purchases, shares — and attribute them back to the link that drove them. Two paths: SDK tracking from your mobile app, or backend webhooks from your server. Both are attributed, deduped, and rolled up into your link stats.

How it works

SDK tracking

Call trackConversion from your iOS or Android app. The SDK handles auth (publishable key), user resolution, and the HTTP call. Best for client-side events like trades, purchases, or in-app actions.

Backend webhooks

POST events to a source webhook URL from your server. Best for server-side events like Stripe payments, RevenueCat callbacks, or admin actions where the SDK isn't involved.

Both paths resolve user_id → attribution → link, dedupe via idempotency key, and roll counts into the link stats endpoint.

Option A: SDK tracking

The fastest path. The mobile SDK handles auth, user resolution, and the HTTP call. One line wherever a conversion happens.

1

Bind the user (prerequisite)

Before you can attribute conversions, call setUserId wherever you handle your user session. See the Lifecycle docs for the full pattern.

// iOS
try? await rift.setUserId(userId: currentUser.id)
// Android
rift.setUserId(userId = currentUser.id)
2

Track a conversion

Call trackConversion whenever a user does something worth counting. The SDK reads the bound user, authenticates with your publishable key, and POSTs to POST /v1/lifecycle/convert.

// iOS — on trade completion, purchase, signup:
try await rift.trackConversion(
    conversionType: "trade",
    idempotencyKey: orderId,
    metadata: ["asset": "ETH", "side": "buy"]
)
// Android
rift.trackConversion(
    conversionType = "trade",
    idempotencyKey = orderId,
    metadata = mapOf("asset" to "ETH", "side" to "buy")
)
Note: The server dedupes via idempotencyKey, so retries are safe. If no user_id is bound, the SDK logs a warning and skips the call.
3

Check your stats

Conversion counts roll up into the funnel stats endpoint immediately:

curl "https://api.riftl.ink/v1/analytics/stats?link_ids=summer-sale" \
  -H "Authorization: Bearer rl_live_YOUR_KEY"

Option B: Backend webhooks

For server-side events — Stripe webhooks, RevenueCat callbacks, admin actions — POST directly to a source webhook URL. No SDK involved, no client-side code.

1

Get your webhook URL

Your tenant's default custom source is auto-provisioned on first request. List your sources to get the URL:

curl https://api.riftl.ink/v1/sources \
  -H "Authorization: Bearer rl_live_YOUR_KEY"
{
  "sources": [
    {
      "id": "66a1b2c3d4e5f6a7b8c9d0e1",
      "name": "default",
      "source_type": "custom",
      "webhook_url": "https://api.riftl.ink/w/a1b2c3d4e5f6...",
      "created_at": "2026-04-10T12:00:00Z"
    }
  ]
}

The webhook_url is the opaque, unguessable URL your backend POSTs events to. Treat it like a secret. To rotate it, delete the source and create a new one.

2

Bind a user to a link (prerequisite)

Before you can attribute conversions, each user needs an attribution record linking them back to the install that originally drove them. The mobile SDK handles this in one line — see the Attribution doc for the full pattern:

// iOS
try? await rift.setUserId("usr_abc123")
// Android
rift.setUserId("usr_abc123")

The SDK persists the binding locally and syncs it to the server, retrying automatically on the next app launch if the network call fails.

Note: Conversions fired with a user_id that has no matching attribution record are silently dropped (the webhook still returns 200, but the event is not counted toward your link stats). Make sure your app calls setUserId before you start firing conversions for that user.
3

Fire a conversion

POST to the source's webhook URL whenever a user does something worth counting. The only required fields are user_id and type. Idempotency key and metadata are optional.

curl -X POST https://api.riftl.ink/w/a1b2c3d4e5f6... \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr_abc123",
    "type": "deposit",
    "idempotency_key": "tx_550e8400-e29b",
    "metadata": { "tx_hash": "0xabc...", "amount": "100.00", "currency": "usd" }
  }'

The response tells you what Rift did with the batch:

{
  "accepted": 1,
  "deduped": 0,
  "unattributed": 0,
  "failed": 0
}
4

Check your stats

The funnel stats endpoint returns a branched response with conversions keyed by type:

curl "https://api.riftl.ink/v1/analytics/stats?link_ids=summer-sale" \
  -H "Authorization: Bearer rl_live_YOUR_KEY"
{
  "from": "2026-04-25T00:00:00Z",
  "to": "2026-05-25T00:00:00Z",
  "link_ids": ["summer-sale"],
  "credit": "last_touch",
  "funnel": {
    "clicks": 1420,
    "new_users": { "installed": 340, "identified": 198 },
    "returning_users": { "reinstalled": 0, "new_device": 0, "engaged": 0 },
    "conversions": { "deposit": 19, "signup": 91 }
  }
}

The event payload

Every custom-source event follows this shape:

{
  "user_id": "usr_abc123",           // required
  "type": "deposit",                 // required — free-form, up to 64 chars
  "idempotency_key": "tx_abc",       // optional, <=256 chars
  "metadata": { "tx_hash": "0x..." } // optional, <=1KB, stored verbatim
}
Note: Need to track revenue or amounts? Put them in metadata — Rift stores it verbatim and forwards it on the outbound webhook. Your warehouse handles the aggregation.

Idempotency key contract

  • Scoped per tenant — two tenants can use the same key without collision.
  • Unique within a 30-day window — after TTL expiry, keys may be safely reused.
  • Opaque to Rift — any string up to 256 characters, not parsed or validated.
  • Collision behavior — Rift silently drops duplicates and returns 200, so your retry logic stays happy. The event is not double-counted.
  • Typical values — on-chain transaction hash, order ID, your DB transaction UUID.
  • Optional — if you omit it, every call counts. That's fine for events where double-counting doesn't matter (e.g. content views), but use a key for anything you need exact counts on.

Managing sources

The default custom source handles the common case — one pipe from your backend to Rift. If you want to segment events by origin (e.g. “backend-deposits” vs “admin-overrides”), create additional custom sources explicitly.

1

Create a source

curl -X POST https://api.riftl.ink/v1/sources \
  -H "Authorization: Bearer rl_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "backend-deposits",
    "source_type": "custom"
  }'
2

Get one source

curl https://api.riftl.ink/v1/sources/SOURCE_ID \
  -H "Authorization: Bearer rl_live_YOUR_KEY"
3

Delete a source

Historical events for the deleted source remain queryable via the link stats endpoint — they still carry the source_id reference even after the source document is gone. There is no rotate endpoint; to rotate a webhook URL, delete the source and create a new one.

curl -X DELETE https://api.riftl.ink/v1/sources/SOURCE_ID \
  -H "Authorization: Bearer rl_live_YOUR_KEY"

Outbound webhook delivery

Every conversion fires an outbound conversion webhook to any registered webhook subscribed to that event type. The payload includes a stable event_id — use it as an idempotency key in your handler to safely dedupe delivery retries. See the Webhooks doc for the full payload shape and signature verification.

Note: Webhook delivery is best-effort with 4 retries. For zero-loss reconciliation, poll GET /v1/analytics/stats?link_ids={link_id} 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.

What Rift answers

Rift's conversion API is deliberately bounded. It answers one class of question well and refuses the rest. If a question starts with “which link”, it's in scope. If it starts with “which user”, that's your warehouse's job — pipe events via webhook.

In scope

  • Total count per link, per conversion type
  • Conversion attribution tied to the originating link
  • Idempotent event ingestion with at-least-once delivery
  • Outbound webhooks for streaming events to your warehouse

Out of scope

  • User-level queries (cohorts, funnels, retention)
  • Filtering or grouping by metadata fields
  • Multi-event behavioral sequences
  • Per-event drill-down from the API

Metadata is stored verbatim and forwarded on the outbound webhook, but it's never indexed or queried inside Rift. Use it for your own debugging and warehouse joins.