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

  1. Create an API key at Settings > API Keys (see Managing API Keys)
  2. Configure your sending system with the webhook URL, POST method, and Content-Type: application/json
  3. Test with a sample request:
bash
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):

json
1{
2  "id": 123,
3  "message": "Signal received and queued for processing",
4  "source": "generic",
5  "cold_storage": false
6}

Request Requirements

Required Headers

HeaderValue
Content-Typeapplication/json

Optional Headers

HeaderDescription
X-Api-SecretShared secret (required if secret is configured on the API key)
X-SourceExplicitly set the signal source (overrides auto-detection)

Response Codes

CodeMeaningAction
202AcceptedSignal received and queued for processing
400Bad RequestCheck payload format / invalid JSON
401UnauthorizedAPI key is invalid, inactive, expired, or missing required secret
429Rate LimitedRaw signal limit reached. Upgrade plan or enable overages
500Server ErrorRetry 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:

json
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:

javascript
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:

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:

javascript
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}

Next Steps