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.clicklink.token_createdlink.claimlink.app_openlink.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
- Go to Portal -> App -> Developer -> Webhooks.
- Enter your webhook URL (
https://...). - Select one or more event types.
- Optionally enable signing secret (auto-generated by LinkMe).
- 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);
}
Node SDK shortcut (recommended)
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:
- Receive LinkMe webhook at your backend endpoint.
- Verify signature (if enabled).
- Validate and parse
event+data. - Push to your destination (PostHog, GA4, queue, warehouse).
- Return
2xxquickly.
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_IDin function env/config - load
GA4_API_SECRETfrom Secret Manager - load
LINKME_WEBHOOK_SIGNING_SECRETfrom 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:
- Add your HTTPS endpoint (example:
/webhooks/linkme). - Subscribe to the events you need (
link.click,link.claim, etc.). - 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)