{"id":1141,"date":"2026-05-20T17:00:00","date_gmt":"2026-05-20T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1141"},"modified":"2026-03-07T03:34:56","modified_gmt":"2026-03-07T08:34:56","slug":"webhook-segment-integration","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/webhook-segment-integration\/","title":{"rendered":"Sending Deep Link Events to Segment via Webhooks"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">If your analytics stack runs through <a href=\"https:\/\/segment.com\/docs\/\" rel=\"nofollow noopener\" target=\"_blank\">Segment<\/a>, your deep link events should too. When deep link clicks, installs, and referrals live in the same CDP as your other product events, you can build cohorts, trigger campaigns, and run attribution analysis without stitching data together from separate sources.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This guide covers how to forward <a href=\"https:\/\/tolinku.com\/features\/webhooks\">Tolinku webhook<\/a> events to Segment using a lightweight receiver. If you haven&#39;t set up webhooks yet, start with the <a href=\"https:\/\/tolinku.com\/blog\/webhook-setup-guide\/\">webhook setup guide<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><img decoding=\"async\" src=\"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/platform-webhooks.png\" alt=\"Tolinku webhook configuration for event notifications\">\n<em>The webhooks page with create form, webhook list, and delivery log.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Segment Instead of Direct Integrations?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You could send webhook events directly to each analytics tool (Amplitude, Mixpanel, BigQuery). We cover that approach in <a href=\"https:\/\/tolinku.com\/blog\/webhooks-analytics-pipelines\/\">webhooks for analytics pipelines<\/a>. The Segment approach adds a layer of indirection, but it has real advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>One integration, many destinations.<\/strong> Forward deep link events to Amplitude, Mixpanel, BigQuery, Redshift, HubSpot, Braze, and dozens of other tools by toggling destinations in Segment&#39;s UI. No code changes needed.<\/li>\n<li><strong>Consistent schema.<\/strong> Segment enforces a schema across all your event sources. Deep link events follow the same structure as your web and mobile events, making cross-platform analysis straightforward.<\/li>\n<li><strong>Retroactive forwarding.<\/strong> When you add a new destination in Segment, you can replay historical events to it. Direct integrations only capture events from the moment they&#39;re connected.<\/li>\n<li><strong>Identity resolution.<\/strong> Segment&#39;s identity graph helps tie anonymous deep link clicks to known users when they eventually authenticate.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The tradeoff is an extra hop (Tolinku sends to your receiver, your receiver sends to Segment, Segment sends to destinations). For most teams, the added latency (milliseconds) is negligible compared to the operational simplicity.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture<\/h2>\n\n\n\n<pre><code>Tolinku Webhook \u2192 Your Receiver (verify + transform) \u2192 Segment HTTP API \u2192 Destinations\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The receiver does three things:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Verifies the <code>X-Webhook-Signature<\/code> header<\/li>\n<li>Transforms the Tolinku event into Segment&#39;s <a href=\"https:\/\/segment.com\/docs\/connections\/spec\/track\/\" rel=\"nofollow noopener\" target=\"_blank\">Track<\/a> call format<\/li>\n<li>Sends the transformed event to Segment&#39;s HTTP Tracking API<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">The Receiver<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#39;s a complete Express server that receives Tolinku webhooks and forwards them to Segment.<\/p>\n\n\n\n<pre><code class=\"language-typescript\">import express from &#39;express&#39;;\nimport crypto from &#39;crypto&#39;;\nimport { Analytics } from &#39;@segment\/analytics-node&#39;;\n\nconst app = express();\napp.use(&#39;\/webhooks&#39;, express.raw({ type: &#39;application\/json&#39; }));\n\nconst WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; \/\/ whsec_...\nconst analytics = new Analytics({ writeKey: process.env.SEGMENT_WRITE_KEY! });\n\napp.post(&#39;\/webhooks\/tolinku&#39;, (req, res) =&gt; {\n  \/\/ Verify signature\n  const signature = req.headers[&#39;x-webhook-signature&#39;] as string;\n  const expected = crypto\n    .createHmac(&#39;sha256&#39;, WEBHOOK_SECRET)\n    .update(req.body)\n    .digest(&#39;hex&#39;);\n\n  if (signature !== expected) {\n    return res.status(401).send(&#39;Invalid signature&#39;);\n  }\n\n  res.status(200).send(&#39;OK&#39;);\n\n  const event = JSON.parse(req.body.toString());\n  forwardToSegment(event);\n});\n\nfunction forwardToSegment(event: any) {\n  const { event: eventType, timestamp, data } = event;\n\n  analytics.track({\n    anonymousId: generateAnonymousId(event),\n    event: mapEventName(eventType),\n    timestamp: new Date(timestamp),\n    properties: {\n      ...data,\n      source: &#39;tolinku_webhook&#39;,\n      original_event_type: eventType,\n    },\n    context: {\n      ip: data.ip,\n      library: {\n        name: &#39;tolinku-webhook-receiver&#39;,\n        version: &#39;1.0.0&#39;,\n      },\n    },\n  });\n}\n\nfunction mapEventName(eventType: string): string {\n  const names: Record&lt;string, string&gt; = {\n    &#39;link.clicked&#39;: &#39;Deep Link Clicked&#39;,\n    &#39;deferred_link.claimed&#39;: &#39;Deferred Link Claimed&#39;,\n    &#39;install.tracked&#39;: &#39;Install Tracked&#39;,\n    &#39;referral.created&#39;: &#39;Referral Created&#39;,\n    &#39;referral.completed&#39;: &#39;Referral Completed&#39;,\n  };\n  return names[eventType] || `Webhook ${eventType}`;\n}\n\nfunction generateAnonymousId(event: any): string {\n  \/\/ Generate a consistent anonymous ID from event data\n  \/\/ This allows Segment to group events from the same source\n  const seed = `${event.data.ip}-${event.data.platform}-${event.data.device_type}`;\n  return crypto.createHash(&#39;sha256&#39;).update(seed).digest(&#39;hex&#39;).substring(0, 32);\n}\n\napp.listen(3000, () =&gt; console.log(&#39;Receiver listening on port 3000&#39;));\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#39;s break down the key decisions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Event Naming<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Segment&#39;s <a href=\"https:\/\/segment.com\/docs\/connections\/spec\/track\/\" rel=\"nofollow noopener\" target=\"_blank\">Track specification<\/a> recommends human-readable event names in Title Case. The mapping converts Tolinku&#39;s internal event types to Segment-friendly names:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Tolinku Event<\/th>\n<th>Segment Event Name<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><code>link.clicked<\/code><\/td>\n<td>Deep Link Clicked<\/td>\n<\/tr>\n<tr>\n<td><code>deferred_link.claimed<\/code><\/td>\n<td>Deferred Link Claimed<\/td>\n<\/tr>\n<tr>\n<td><code>install.tracked<\/code><\/td>\n<td>Install Tracked<\/td>\n<\/tr>\n<tr>\n<td><code>referral.created<\/code><\/td>\n<td>Referral Created<\/td>\n<\/tr>\n<tr>\n<td><code>referral.completed<\/code><\/td>\n<td>Referral Completed<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This follows Segment&#39;s <a href=\"https:\/\/segment.com\/docs\/getting-started\/04-full-install\/#1-naming-conventions\" rel=\"nofollow noopener\" target=\"_blank\">naming conventions<\/a>: object + past-tense verb. Keeping event names consistent with this pattern means they&#39;ll feel natural alongside your other Segment events.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Identity: Anonymous vs. Known Users<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Deep link webhook events fire at the link level, not the user level. When someone clicks a deep link, Tolinku doesn&#39;t necessarily know who they are. This is why the receiver uses <code>anonymousId<\/code> rather than <code>userId<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>generateAnonymousId<\/code> function creates a consistent hash from the IP, platform, and device type. This isn&#39;t a true user identity, but it groups events from the same source together. In Segment, these anonymous events can later be merged with identified user profiles through Segment&#39;s <a href=\"https:\/\/segment.com\/docs\/unify\/identity-resolution\/\" rel=\"nofollow noopener\" target=\"_blank\">identity resolution<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your deep links carry a user identifier (for example, a referral token that maps to a known user), you can resolve that on the receiver side and pass a <code>userId<\/code> instead:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">function forwardToSegment(event: any) {\n  const { event: eventType, timestamp, data } = event;\n\n  \/\/ If we can resolve a known user from the event data\n  const userId = resolveUserId(data);\n\n  analytics.track({\n    ...(userId ? { userId } : { anonymousId: generateAnonymousId(event) }),\n    event: mapEventName(eventType),\n    timestamp: new Date(timestamp),\n    properties: {\n      ...data,\n      source: &#39;tolinku_webhook&#39;,\n      original_event_type: eventType,\n    },\n  });\n}\n\nfunction resolveUserId(data: any): string | null {\n  \/\/ Example: look up user by referral token\n  \/\/ This would query your database\n  return null;\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Properties and Context<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>properties<\/code> object passes all event data fields through to Segment. The spread operator (<code>...data<\/code>) forwards every field from the Tolinku payload without hardcoding specific field names. This means when Tolinku adds new fields to event payloads, they automatically flow through to Segment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two additional fields are added:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>source: &#39;tolinku_webhook&#39;<\/code> lets you filter these events in Segment and downstream tools<\/li>\n<li><code>original_event_type<\/code> preserves the raw Tolinku event type for reference<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>context<\/code> object includes the IP address (Segment uses this for geo-enrichment) and a library identifier so you can distinguish these events from client-side SDK events in Segment&#39;s debugger.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Idempotency<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Tolinku retries failed webhook deliveries (3 retries at 1 minute, 5 minutes, and 30 minutes). If your receiver was temporarily down, you&#39;ll receive the same event multiple times when it recovers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Segment handles deduplication through the <a href=\"https:\/\/segment.com\/docs\/connections\/spec\/common\/#context\" rel=\"nofollow noopener\" target=\"_blank\">messageId<\/a> field. The <code>@segment\/analytics-node<\/code> library generates a unique <code>messageId<\/code> for each <code>track()<\/code> call by default. To make retries idempotent, generate a deterministic <code>messageId<\/code> from the event content:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">function forwardToSegment(event: any) {\n  const messageId = crypto\n    .createHash(&#39;sha256&#39;)\n    .update(JSON.stringify(event))\n    .digest(&#39;hex&#39;);\n\n  analytics.track({\n    messageId,\n    anonymousId: generateAnonymousId(event),\n    event: mapEventName(event.event),\n    timestamp: new Date(event.timestamp),\n    properties: {\n      ...event.data,\n      source: &#39;tolinku_webhook&#39;,\n    },\n  });\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now if the same event is received twice (due to a retry), the same <code>messageId<\/code> is generated, and Segment deduplicates it. See the <a href=\"https:\/\/tolinku.com\/blog\/webhook-retry-logic\/\">webhook retry logic guide<\/a> for more on handling retries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deploying the Receiver<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The receiver is a small, stateless Express server. It&#39;s a good candidate for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AWS Lambda + API Gateway<\/strong>: Use the <code>serverless-http<\/code> wrapper. The receiver handles one request at a time and doesn&#39;t need persistent state.<\/li>\n<li><strong>Google Cloud Run<\/strong>: Deploy as a container. Cloud Run scales to zero when idle and spins up on demand.<\/li>\n<li><strong>Fly.io or Railway<\/strong>: Simple deployment from a Dockerfile. Good for teams that want a straightforward hosting experience.<\/li>\n<li><strong>Your existing server<\/strong>: If you already run a Node.js backend, add the webhook route to it.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The receiver needs two environment variables:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>WEBHOOK_SECRET<\/code>: Your Tolinku webhook signing secret (<code>whsec_...<\/code>)<\/li>\n<li><code>SEGMENT_WRITE_KEY<\/code>: Your Segment source write key<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For production, add health checks, structured logging, and error monitoring. A dead receiver means missed events (though Tolinku&#39;s retry logic provides a buffer).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the Integration<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Configure the webhook<\/strong> in your Tolinku Appspace pointing to your receiver URL<\/li>\n<li><strong>Send a test webhook<\/strong> using the Test button in the Tolinku dashboard<\/li>\n<li><strong>Check Segment&#39;s debugger<\/strong> (<a href=\"https:\/\/app.segment.com\" rel=\"nofollow noopener\" target=\"_blank\">app.segment.com<\/a> &gt; Sources &gt; your source &gt; Debugger) to confirm the event arrived<\/li>\n<li><strong>Click a real deep link<\/strong> and verify the <code>Deep Link Clicked<\/code> event appears in Segment with the correct properties<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Common issues:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>No events in Segment debugger<\/strong>: Check that your <code>SEGMENT_WRITE_KEY<\/code> is correct and the receiver logs show successful forwarding<\/li>\n<li><strong>Events arrive but properties are empty<\/strong>: Make sure you&#39;re parsing the raw body correctly (<code>JSON.parse(req.body.toString())<\/code>)<\/li>\n<li><strong>Duplicate events<\/strong>: Implement the deterministic <code>messageId<\/code> approach described above<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Segment Destinations for Deep Link Events<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once events flow into Segment, enable destinations to forward them automatically:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Destination<\/th>\n<th>Use Case<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Amplitude \/ Mixpanel<\/td>\n<td>Product analytics, funnel analysis<\/td>\n<\/tr>\n<tr>\n<td>BigQuery \/ Snowflake<\/td>\n<td>Data warehouse for long-term storage and SQL queries<\/td>\n<\/tr>\n<tr>\n<td>HubSpot \/ Salesforce<\/td>\n<td>CRM attribution for referral-driven signups<\/td>\n<\/tr>\n<tr>\n<td>Braze \/ Iterable<\/td>\n<td>Trigger re-engagement campaigns based on deep link behavior<\/td>\n<\/tr>\n<tr>\n<td>Google Analytics 4<\/td>\n<td>Attribution reporting alongside web analytics<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Each destination receives the same event data. You configure field mappings in Segment&#39;s destination settings, not in your receiver code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Volume Considerations<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>@segment\/analytics-node<\/code> library batches events automatically (default: 15 events or 5 seconds, whichever comes first). For high-volume webhook traffic, you can tune the batch settings:<\/p>\n\n\n\n<pre><code class=\"language-typescript\">const analytics = new Analytics({\n  writeKey: process.env.SEGMENT_WRITE_KEY!,\n  flushAt: 50,       \/\/ Send when batch reaches 50 events\n  flushInterval: 2000, \/\/ Or every 2 seconds\n});\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Segment&#39;s HTTP Tracking API has a rate limit of 500 requests per second for most plans. Each batch counts as one request, so batching 50 events per request gives you an effective throughput of 25,000 events per second, which is well beyond what most webhook integrations produce.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the full picture of connecting webhooks to analytics tools, see the <a href=\"https:\/\/tolinku.com\/blog\/webhooks-analytics-pipelines\/\">webhooks for analytics pipelines<\/a> guide. For no-code alternatives, see <a href=\"https:\/\/tolinku.com\/blog\/webhook-zapier-integration\/\">connecting deep links to Zapier<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Stream deep link events to Segment for unified analytics. Configure webhooks to send click, install, and referral data to your CDP in real time.<\/p>\n","protected":false},"author":2,"featured_media":1140,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Sending Deep Link Events to Segment via Webhooks","rank_math_description":"Stream deep link events to Segment for unified analytics. Configure webhooks to send click, install, and referral data to your CDP in real time.","rank_math_focus_keyword":"webhook Segment integration","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-webhook-segment-integration.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-webhook-segment-integration.png","footnotes":""},"categories":[15],"tags":[37,284,276,20,264,263,283,61],"class_list":["post-1141","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-analytics","tag-cdp","tag-data-pipelines","tag-deep-linking","tag-engineering","tag-integrations","tag-segment","tag-webhooks"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1141","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1141"}],"version-history":[{"count":2,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1141\/revisions"}],"predecessor-version":[{"id":2256,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1141\/revisions\/2256"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1140"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}