Skip to main content

Webhooks Guide

Use LinkMe webhooks when you want near real-time event delivery to your own systems or third-party tools.

You can subscribe to:

  • link.click
  • link.token_created
  • link.claim
  • link.app_open
  • link.deferred_claim_attempt

What this is for

Webhooks let you:

  • stream LinkMe events into your backend
  • trigger automation (CRM, notifications, workflows)
  • forward events into analytics pipelines (PostHog, GA4, Segment, warehouse)
  • keep a delivery audit trail without polling APIs

Configure a webhook in Portal

  1. Go to Portal -> App -> Developer -> Webhooks.
  2. Enter your webhook URL (https://...).
  3. Select one or more event types.
  4. Optionally enable signing secret (auto-generated by LinkMe).
  5. Save.

Each webhook can be enabled/disabled independently.

Payload shape

Each request body follows this envelope:

{
"id": "uuid",
"event": "link.click",
"ts": "2026-02-11T12:34:56.789Z",
"app_id": "app_123",
"data": {
"link_id": "abc123",
"platform": "ios",
"dest_url": "https://example.com"
}
}

data fields vary by event type.

Delivery behavior

LinkMe delivery behavior includes:

  • automatic retries for retriable errors (for example 5xx, timeouts)
  • backoff between retry attempts
  • final stop after max attempts
  • per-attempt delivery history with status and response snippet
  • auto-disable if failures keep repeating

You can inspect recent deliveries in Developer -> Webhooks -> Deliveries.

Optional request signing

If signing is enabled, LinkMe auto-generates a secret for the webhook and includes:

  • X-LinkMe-Signature: sha256=<hmac>

The signature is HMAC-SHA256 over the raw request body using your secret.

Node.js verification example

import crypto from 'node:crypto';

export function verifyLinkMeSignature(rawBody: string, secret: string, signatureHeader: string): boolean {
if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false;
const receivedHex = signatureHeader.slice('sha256='.length);
const expectedHex = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = Buffer.from(receivedHex, 'hex');
const expected = Buffer.from(expectedHex, 'hex');
if (received.length !== expected.length) return false;
return crypto.timingSafeEqual(received, expected);
}

If you use @li-nk.me/node-sdk, use built-in helpers instead of maintaining custom verification code:

import {
parseLinkMeWebhookEnvelope,
verifyLinkMeWebhookSignature,
} from '@li-nk.me/node-sdk';

const rawBody = req.rawBody.toString('utf8');
const signature = req.get('X-LinkMe-Signature');

if (!verifyLinkMeWebhookSignature(rawBody, signature, process.env.LINKME_WEBHOOK_SIGNING_SECRET!)) {
return res.status(401).json({ ok: false, error: 'Invalid signature' });
}

const envelope = parseLinkMeWebhookEnvelope(JSON.parse(rawBody));
// forward `envelope` to your own pipeline (queue, analytics provider, data warehouse, etc.)

return res.status(200).json({ ok: true });

Python verification example

import hmac
import hashlib

def verify_linkme_signature(raw_body: bytes, secret: str, signature_header: str) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
received_hex = signature_header[len("sha256="):]
expected_hex = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(received_hex, expected_hex)

Common integration pattern

Typical production setup:

  1. Receive LinkMe webhook at your backend endpoint.
  2. Verify signature (if enabled).
  3. Validate and parse event + data.
  4. Push to your destination (PostHog, GA4, queue, warehouse).
  5. Return 2xx quickly.

Keep heavy downstream processing async in your worker/queue layer.

GA4 server-side setup (Measurement Protocol)

This is the setup used when LinkMe webhook events are forwarded from your backend to GA4 as server-side events.

1) Create/get the GA4 Measurement ID

In GA4, open your property and select a data stream. Copy the stream's Measurement ID (format: G-XXXXXXXXXX).

Store this as:

  • GA4_MEASUREMENT_ID (regular environment variable)

2) Create a GA4 API secret

For the same data stream, open Measurement Protocol API secrets and create a new secret.

Store this as:

  • GA4_API_SECRET (secret, not plain env)

3) Configure LinkMe webhook signature secret

If webhook signing is enabled in LinkMe, copy the signing secret and store it as:

  • LINKME_WEBHOOK_SIGNING_SECRET (secret)

Your receiver should verify:

  • X-LinkMe-Signature: sha256=<hmac>

against the raw request body.

4) Deploy backend config

For a Firebase Functions backend, a common setup is:

  • keep GA4_MEASUREMENT_ID in function env/config
  • load GA4_API_SECRET from Secret Manager
  • load LINKME_WEBHOOK_SIGNING_SECRET from Secret Manager
  • ensure runtime service account has roles/secretmanager.secretAccessor

Example (Google Cloud CLI):

gcloud secrets create GA4_API_SECRET --replication-policy="automatic"
printf '%s' 'YOUR_GA4_API_SECRET' | gcloud secrets versions add GA4_API_SECRET --data-file=-

gcloud secrets create LINKME_WEBHOOK_SIGNING_SECRET --replication-policy="automatic"
printf '%s' 'YOUR_LINKME_SIGNING_SECRET' | gcloud secrets versions add LINKME_WEBHOOK_SIGNING_SECRET --data-file=-

5) Configure LinkMe webhook endpoint

In LinkMe Portal:

  1. Add your HTTPS endpoint (example: /webhooks/linkme).
  2. Subscribe to the events you need (link.click, link.claim, etc.).
  3. Enable signing for production.

6) Make the data meaningful in GA4

Forwarding events is not enough by itself. To make reports usable:

  • keep stable event names (for example linkme_link_click, linkme_link_claim)
  • include stable params such as linkme_event, linkme_event_id, linkme_app_id, linkme_link_id, linkme_platform
  • register important params as Custom dimensions in GA4
  • mark business-critical events (for example claim/activation) as Key events
  • optionally enable BigQuery export for deeper attribution analysis

Quick verification checklist

  • webhook delivery in LinkMe shows 2xx
  • backend logs show successful GA4 Measurement Protocol POST
  • event appears in GA4 Realtime
  • custom dimensions start populating in standard reports (can take time)