Webhooks
Webhooks let you react to events in real time — installs, opens, post-install events, and SKAdNetwork postbacks. Send raw JSON to your own endpoint with HMAC signing, or use a built-in template to push rich messages directly to Slack, Discord, or Teams with no code.
Quick start
Section titled “Quick start”-
Get your webhook URL
Go to api.slack.com/apps → Create New App → From Scratch. Pick a workspace, then go to Incoming Webhooks → Activate → Add New Webhook to Workspace → pick a channel → copy the URL.
https://hooks.slack.com/services/T.../B.../xxxIn your server, right-click a channel → Edit Channel → Integrations → Webhooks → New Webhook → copy the URL.
https://discord.com/api/webhooks/123/abcIn a channel, click … → Connectors (or Workflows in newer Teams) → Incoming Webhook → name it → copy the URL.
Use any HTTPS endpoint that returns
2xx. You’ll verify requests using HMAC-SHA256 signatures. -
Create the webhook
Terminal window trace webhooks create \--url https://hooks.slack.com/services/T.../B.../xxx \--template slackTerminal window trace webhooks create \--url https://discord.com/api/webhooks/123/abc \--template discordTerminal window trace webhooks create \--url https://YOUR_TEAMS_WEBHOOK_URL \--template teamsTerminal window trace webhooks create \--url https://example.com/webhooks/trace \--secret whsec_your_signing_secret -
Send a test event
Terminal window trace webhooks test <webhook-id>You’ll see a rich message appear in your channel (for templated webhooks) or a JSON POST hit your endpoint (for custom webhooks).
-
Done! Trace will now fire webhooks whenever installs, opens, or events are recorded for your app.
Event types
Section titled “Event types”Webhooks fire for these events:
| Event | When it fires |
|---|---|
install.attributed | An install is matched to a click (campaign attribution) |
install.organic | An install is recorded with no matching click |
open.attributed | An app open is matched to a click (deep link open) |
event.recorded | A post-install event is tracked via trackEvent() |
skan.postback_received | An Apple SKAdNetwork postback arrives |
test.webhook | You call trace webhooks test |
Filtering events
Section titled “Filtering events”By default, webhooks receive all event types. To subscribe to specific events only:
# Only attributed installstrace webhooks create \ --url https://hooks.slack.com/services/T.../B.../xxx \ --template slack \ --events install.attributed
# Installs and eventstrace webhooks create \ --url https://hooks.slack.com/services/T.../B.../xxx \ --template slack \ --events install.attributed,install.organic,event.recordedTemplates
Section titled “Templates”Templates transform webhook payloads into platform-native rich messages with color-coded sidebars, structured fields, and event-specific formatting.
Available templates
Section titled “Available templates”trace webhooks templates| Template | Platform | Format |
|---|---|---|
slack | Slack | Block Kit with color-coded sidebar |
discord | Discord | Rich embeds with inline fields |
teams | Microsoft Teams | Adaptive Card v1.4 |
Color coding
Section titled “Color coding”Each event type has a distinct color so you can scan channels at a glance:
| Event | Color | Meaning |
|---|---|---|
install.attributed | Green | A campaign drove this install |
install.organic | Gray | Organic install, no campaign match |
open.attributed | Blue | Deep link open from a campaign |
event.recorded | Purple | Post-install event (purchase, signup, etc.) |
skan.postback_received | Amber | Apple SKAdNetwork postback |
test.webhook | Slate | Test event |
Sandbox indicator
Section titled “Sandbox indicator”Webhook titles include a (Sandbox) suffix when the event came from a sandbox link. This makes it easy to distinguish test traffic from production data in your channels.
Template config
Section titled “Template config”Optionally customize templates with templateConfig:
| Option | Type | Description |
|---|---|---|
mentionText | string | Ping users or groups (e.g. @channel for Slack, @everyone for Discord) |
accentColor | string | Override the default event color (hex, e.g. #ff0000) |
curl -X POST https://api.traceclick.io/v1/webhooks \ -H "X-Api-Key: tr_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.slack.com/services/T.../B.../xxx", "template": "slack", "templateConfig": {"mentionText": "@channel"} }'Custom endpoints
Section titled “Custom endpoints”For your own HTTP endpoints, Trace sends a JSON envelope with HMAC-SHA256 signing.
Envelope format
Section titled “Envelope format”{ "event": "install.attributed", "timestamp": "2025-07-15T14:30:00.000+00:00", "data": { "installId": "550e8400-...", "method": "FINGERPRINT", "campaignId": "summer_sale", "platform": "IOS", "appName": "My App" }}Verifying signatures
Section titled “Verifying signatures”Every webhook POST includes an X-Trace-Signature header — an HMAC-SHA256 hex digest of the raw body, signed with your webhook secret.
import crypto from 'node:crypto';
function verifySignature(body, secret, signature) { const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) );}
// In your handler:const body = await request.text();const sig = request.headers.get('X-Trace-Signature');if (!verifySignature(body, process.env.WEBHOOK_SECRET, sig)) { return new Response('Invalid signature', { status: 401 });}const event = JSON.parse(body);import hmac, hashlib
def verify_signature(body: bytes, secret: str, signature: str) -> bool: expected = hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature)
# In your handler:body = await request.body()sig = request.headers["X-Trace-Signature"]if not verify_signature(body, WEBHOOK_SECRET, sig): return Response("Invalid signature", status_code=401)event = json.loads(body)Request headers
Section titled “Request headers”| Header | Description |
|---|---|
Content-Type | application/json |
X-Trace-Event | Event type (e.g. install.attributed) |
X-Trace-Signature | HMAC-SHA256 hex digest of the raw body |
Managing webhooks
Section titled “Managing webhooks”Each app can have multiple webhooks, each with its own URL, event filter, and template.
# List all webhookstrace webhooks list
# Update a webhooktrace webhooks update <id> --events install.attributed,open.attributedtrace webhooks update <id> --enabled=false
# Delete a webhooktrace webhooks delete <id>
# View delivery historytrace webhooks deliveriestrace webhooks deliveries --event-type install.attributed --limit 10Retry behavior
Section titled “Retry behavior”| Property | Value |
|---|---|
| Timeout | 10 seconds per attempt |
| Max attempts | 3 |
| Backoff | 1s, 5s, 25s (exponential) |
| Success | Any 2xx status code |
If all 3 attempts fail, the failure is recorded in the delivery log. Every attempt (success or failure) is queryable via trace webhooks deliveries.
Payload reference
Section titled “Payload reference”For the full list of event payloads with field-by-field documentation, see the Webhooks API reference.
Example: Slack notifications for attributed installs
Section titled “Example: Slack notifications for attributed installs”A common setup: get a Slack notification whenever a campaign drives an install.
# Create a Slack webhook for attributed installs onlytrace webhooks create \ --url https://hooks.slack.com/services/T.../B.../xxx \ --template slack \ --events install.attributed
# Verify it workstrace webhooks test <webhook-id>The message will show the attribution method, campaign ID, platform, and deep link path — color-coded green for attributed installs.
Example: All events to a custom endpoint
Section titled “Example: All events to a custom endpoint”For full control, send all events to your own server with HMAC signing:
trace webhooks create \ --url https://api.yourapp.com/webhooks/trace \ --secret whsec_$(openssl rand -hex 20)Then in your handler, switch on the event type:
app.post('/webhooks/trace', (req, res) => { // Verify signature first (see above)
const { event, data } = req.body; switch (event) { case 'install.attributed': analytics.track('attributed_install', { campaign: data.campaignId, method: data.method, }); break; case 'event.recorded': if (data.eventName === 'purchase_completed') { crm.tagUser(data.userId, 'paying_customer'); } break; }
res.sendStatus(200);});