# Configuring Webhooks

> Source: https://docs.trailspark.ai/docs/webhook-configuration

## 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](/docs/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
curl -X POST \
  "https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "event": "test"}'
```

Expected response (HTTP 202):
```json
{
  "id": 123,
  "message": "Signal received and queued for processing",
  "source": "generic",
  "cold_storage": false
}
```

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

```json
{
  "signals": [
    {"email": "lead1@example.com", "event": "page_view", "properties": {"page": "/pricing"}},
    {"email": "lead2@example.com", "event": "form_submission", "properties": {"form": "contact"}}
  ]
}
```

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
const axios = require('axios');

await axios.post(
  'https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY',
  { email, event, properties }
);
```

**Python:**
```python
import requests

requests.post(
    'https://app.trailspark.ai/api/signal-staging/webhook/YOUR_API_KEY',
    json={'email': email, 'event': event, 'properties': properties}
)
```

## Retry Strategy

Implement exponential backoff for 5xx errors:

```javascript
async function sendWithRetry(url, payload, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });
      if (response.ok) return response;
      if (response.status < 500) throw new Error(`Client error: ${response.status}`);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
    }
  }
}
```

## Next Steps

- [Webhook Payload Format](/docs/webhook-payload-format)
- [API Keys](/docs/api-keys)
- [Creating Signal Mapping](/docs/creating-signal-mapping)