Configuring Webhooks
Webhook Endpoints
TrailSpark provides several endpoint formats for signal ingestion:
Universal Webhook
POST https://app.trailspark.ai/api/signal-staging/webhook/{apiKey}
Accepts signals from any source. Source identification comes from the payload.
Source-Specific Webhook
POST https://app.trailspark.ai/api/signal-staging/webhook/{source}/{apiKey}
Include a source identifier in the URL (e.g., website, forms, marketing, custom). Useful when multiple systems send to the same key and you want to filter by source.
Batch Webhook
POST https://app.trailspark.ai/api/signal-staging/webhook/{apiKey}/batch
Send an array of signals in a single request.
Bulk Ingestion
POST https://app.trailspark.ai/api/signal-staging/bulk/{apiKey}
For historical data imports and high-volume batch processing.
Setup
- Create an API key at Settings > API Keys (see Managing API Keys)
- Configure your sending system with the webhook URL,
POSTmethod, andContent-Type: application/json - Test with a sample request:
1curl -X POST \
2 "https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{"email": "test@example.com", "event": "test"}'Expected response (HTTP 202):
1{
2 "id": 123,
3 "message": "Signal received and queued for processing",
4 "source": "generic",
5 "cold_storage": false
6}Request Requirements
Required Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Optional Headers
| Header | Description |
|---|---|
X-Api-Secret | Shared secret (required if secret is configured on the API key) |
X-Source | Explicitly set the signal source (overrides auto-detection) |
Response Codes
| Code | Meaning | Action |
|---|---|---|
| 202 | Accepted | Signal received and queued for processing |
| 400 | Bad Request | Check payload format / invalid JSON |
| 401 | Unauthorized | API key is invalid, inactive, expired, or missing required secret |
| 429 | Rate Limited | Raw signal limit reached. Upgrade plan or enable overages |
| 500 | Server Error | Retry with exponential backoff |
Rate Limits
Rate limits are configured per organization via the rateLimits table. When limits are exceeded, requests receive 429 status codes.
For high-volume use, prefer the batch (/webhook/{apiKey}/batch) or bulk (/bulk/{apiKey}) endpoint to reduce request count.
Bulk Payload Format
Wrap multiple signals in a signals array:
1{
2 "signals": [
3 {"email": "lead1@example.com", "event": "page_view", "properties": {"page": "/pricing"}},
4 {"email": "lead2@example.com", "event": "form_submission", "properties": {"form": "contact"}}
5 ]
6}Each signal in the array is processed individually, with separate cold storage routing and usage tracking per signal.
Server-Side Integration Examples
Node.js:
1const axios = require('axios');
2
3await axios.post(
4 'https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY',
5 { email, event, properties }
6);Python:
1import requests
2
3requests.post(
4 'https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY',
5 json={'email': email, 'event': event, 'properties': properties}
6)Retry Strategy
Implement exponential backoff for 5xx errors:
1async function sendWithRetry(url, payload, maxRetries = 3) {
2 for (let i = 0; i < maxRetries; i++) {
3 try {
4 const response = await fetch(url, {
5 method: 'POST',
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify(payload)
8 });
9 if (response.ok) return response;
10 if (response.status < 500) throw new Error(`Client error: ${response.status}`);
11 } catch (error) {
12 if (i === maxRetries - 1) throw error;
13 await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
14 }
15 }
16}