Skip to content
Tolinku
Tolinku
Sign In Start Free
Engineering · · 7 min read

Webhook Payload Formats: Parsing Deep Link Event Data

By Tolinku Staff
|
Tolinku webhooks integrations dashboard screenshot for engineering blog posts

Every webhook integration starts with the same question: what does the payload look like? This reference covers the exact structure of every Tolinku webhook event, with TypeScript types, field descriptions, and parsing patterns. Bookmark this page. You'll come back to it every time you add a new event handler.

For setup instructions, see the webhook setup guide. For the full list of events and when they fire, see the webhook event types guide.

Tolinku webhook configuration for event notifications The webhooks page with create form, webhook list, and delivery log.

Envelope Structure

Every webhook delivery uses the same envelope format, regardless of event type:

{
  "event": "link.clicked",
  "timestamp": "2026-05-22T14:30:00.000Z",
  "data": { ... }
}
Field Type Description
event string The event type identifier
timestamp string (ISO 8601) When the event occurred, in UTC
data object Event-specific payload data

The event field is also sent in the X-Webhook-Event HTTP header, so you can route events before parsing the body.

TypeScript Types

interface WebhookEnvelope {
  event: WebhookEventType;
  timestamp: string; // ISO 8601
  data: Record<string, unknown>;
}

type WebhookEventType =
  | 'link.clicked'
  | 'deferred_link.claimed'
  | 'install.tracked'
  | 'referral.created'
  | 'referral.completed'
  | 'test';

HTTP Headers

Every webhook delivery includes these headers:

Header Example Description
Content-Type application/json Always JSON
X-Webhook-Signature a1b2c3d4e5... HMAC-SHA256 hex digest of the request body
X-Webhook-Event link.clicked The event type (matches event field in body)
User-Agent Tolinku/1.0 Identifies the request as from Tolinku

The signature is computed as:

import crypto from 'crypto';

const signature = crypto
  .createHmac('sha256', webhookSecret) // Your whsec_... secret
  .update(rawRequestBody)              // The raw bytes, not parsed JSON
  .digest('hex');

See the webhook security guide for full verification code.

Fires when a user taps or clicks a deep link managed by Tolinku.

{
  "event": "link.clicked",
  "timestamp": "2026-05-22T14:30:00.000Z",
  "data": {
    "prefix": "go",
    "token": "summer-sale",
    "hostname": "links.example.com",
    "ip": "203.0.113.42",
    "platform": "ios",
    "device_type": "mobile",
    "campaign": "instagram-story-may"
  }
}

Field Reference

Field Type Description
data.prefix string The route prefix (the first path segment of the deep link URL)
data.token string The route token (the second path segment, identifying the specific link)
data.hostname string The hostname the link was served from
data.ip string The IP address of the user who clicked
data.platform string Detected platform: ios, android, or web
data.device_type string Device classification: mobile, tablet, or desktop
data.campaign string or null Campaign identifier if one was configured on the route

TypeScript

interface LinkClickedData {
  prefix: string;
  token: string;
  hostname: string;
  ip: string;
  platform: 'ios' | 'android' | 'web';
  device_type: 'mobile' | 'tablet' | 'desktop';
  campaign: string | null;
}

Notes

  • This is the highest-volume event. A popular link can generate thousands of clicks per day.
  • The campaign field is only present if a campaign was set on the route in your Appspace. It will be null otherwise.
  • The prefix and token together reconstruct the link path: https://{hostname}/{prefix}/{token}.

Fires when a deferred deep link is resolved after app installation. This means a user who clicked a link before having the app installed has now installed and opened the app, and the original click context has been matched to this install.

{
  "event": "deferred_link.claimed",
  "timestamp": "2026-05-22T14:35:00.000Z",
  "data": {
    "prefix": "go",
    "token": "summer-sale",
    "platform": "ios"
  }
}

Field Reference

Field Type Description
data.prefix string The route prefix from the original click
data.token string The route token from the original click
data.platform string The platform the app was installed on

TypeScript

interface DeferredLinkClaimedData {
  prefix: string;
  token: string;
  platform: 'ios' | 'android';
}

Notes

  • This event is the "proof" that a pre-install click resulted in an actual app open.
  • The data is sparser than link.clicked because it represents the resolution, not the original click. Match it to the original click using prefix and token.
  • Deferred link resolution uses the attribution matching logic described in the deferred deep linking guide.

Event: install.tracked

Fires when an app install is attributed to a deep link.

{
  "event": "install.tracked",
  "timestamp": "2026-05-22T14:35:01.000Z",
  "data": {
    "prefix": "go",
    "token": "summer-sale",
    "platform": "ios",
    "campaign": "instagram-story-may"
  }
}

Field Reference

Field Type Description
data.prefix string The route prefix that drove the install
data.token string The route token that drove the install
data.platform string The platform the app was installed on
data.campaign string or null Campaign identifier from the original link

TypeScript

interface InstallTrackedData {
  prefix: string;
  token: string;
  platform: 'ios' | 'android';
  campaign: string | null;
}

Notes

  • This event often fires within seconds of deferred_link.claimed, but they are separate events with distinct purposes. deferred_link.claimed means the link was resolved; install.tracked means the install was attributed.
  • Use this event for user acquisition metrics: installs per campaign, installs per platform, cost per install (when combined with ad spend data).

Event: referral.created

Fires when a new referral is registered. The referred user clicked a referral link and initiated the signup process.

{
  "event": "referral.created",
  "timestamp": "2026-05-22T15:00:00.000Z",
  "data": {
    "referrer_token": "user-abc123",
    "platform": "android"
  }
}

Field Reference

Field Type Description
data.referrer_token string The referral token identifying the referrer
data.platform string The platform the referred user is on

TypeScript

interface ReferralCreatedData {
  referrer_token: string;
  platform: 'ios' | 'android' | 'web';
}

Notes

  • The referrer_token maps to the user who created the referral link. Use it to look up the referrer in your database.
  • This event fires on referral creation, not on completion. The referred user may not yet have finished the required action (e.g., first purchase).

Event: referral.completed

Fires when a referred user completes the target action defined in your referral program.

{
  "event": "referral.completed",
  "timestamp": "2026-05-22T15:30:00.000Z",
  "data": {
    "referrer_token": "user-abc123",
    "platform": "android"
  }
}

Field Reference

Field Type Description
data.referrer_token string The referral token identifying the referrer
data.platform string The platform the referred user converted on

TypeScript

interface ReferralCompletedData {
  referrer_token: string;
  platform: 'ios' | 'android' | 'web';
}

Notes

  • This is the bottom of the referral funnel. It means both parties have done their part: the referrer shared the link, and the referred user completed the target action.
  • Use this event to trigger reward distribution, update CRM records, or send confirmation emails.

Event: test

Fires when you click the Test button in the Tolinku dashboard. Used to verify your endpoint is reachable and your signature verification works.

{
  "event": "test",
  "timestamp": "2026-05-22T10:00:00.000Z",
  "data": {
    "message": "This is a test webhook from Tolinku.",
    "webhook_id": "wh_abc123",
    "appspace_id": "as_xyz789"
  }
}

TypeScript

interface TestData {
  message: string;
  webhook_id: string;
  appspace_id: string;
}

Parsing Best Practices

1. Parse After Verifying

Always verify the X-Webhook-Signature against the raw body before parsing JSON. If you parse first, you might compute the signature on a re-serialized string that differs from the original.

// Correct: verify raw, then parse
app.use('/webhooks', express.raw({ type: 'application/json' }));

app.post('/webhooks/tolinku', (req, res) => {
  if (!verifySignature(req.body, req.headers['x-webhook-signature'])) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body.toString());
  // Process event...
});

2. Handle Unknown Events Gracefully

New event types may be added in the future. Don't crash on unrecognized events:

function handleEvent(event: WebhookEnvelope) {
  switch (event.event) {
    case 'link.clicked':
      handleClick(event.data as LinkClickedData);
      break;
    case 'install.tracked':
      handleInstall(event.data as InstallTrackedData);
      break;
    // ... other known events
    default:
      console.log(`Unknown event type: ${event.event}`);
      // Log it but don't fail
  }
}

3. Handle Missing Fields

Not all fields are guaranteed to be present on every event. The campaign field, for example, is only populated if a campaign was configured on the route. Always use optional chaining or null checks:

const campaign = event.data.campaign || 'unknown';
const platform = event.data.platform ?? 'unknown';

4. Use the Header for Routing

If you need to route events before parsing the body (e.g., to different queue topics), use the X-Webhook-Event header:

app.post('/webhooks/tolinku', (req, res) => {
  const eventType = req.headers['x-webhook-event'] as string;

  // Route to different queues based on event type
  switch (eventType) {
    case 'link.clicked':
      clickQueue.push(req.body);
      break;
    case 'install.tracked':
      installQueue.push(req.body);
      break;
    default:
      defaultQueue.push(req.body);
  }

  res.status(200).send('OK');
});

Delivery Characteristics

  • Timeout: Tolinku waits 10 seconds for a response. If your endpoint doesn't respond within 10 seconds, the delivery is marked as failed and retried.
  • Retries: 3 retries at 1 minute, 5 minutes, and 30 minutes after a failure.
  • No redirects: Tolinku does not follow HTTP redirects. Your endpoint must respond directly.
  • HTTPS only: Production webhooks are delivered over HTTPS.
  • Content-Type: Always application/json.
  • Method: Always POST.

For debugging delivery issues, see the webhook debugging guide. For testing your payload parsing before going to production, see the webhook testing tools guide.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.