Routes API
Routes connect sources to destinations.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/routes | List routes |
| POST | /api/routes | Create route |
| GET | /api/routes/{id} | Get route |
| PATCH | /api/routes/{id} | Update route |
| DELETE | /api/routes/{id} | Delete route |
| GET | /api/routes/{id}/circuit-status | Get circuit breaker status |
| POST | /api/routes/{id}/reset-circuit | Reset circuit breaker |
| PATCH | /api/routes/{id}/circuit-config | Update circuit breaker config |
| GET | /api/routes/export | Export routes as JSON |
| POST | /api/routes/import | Import routes from JSON |
| POST | /api/routes/bulk-pause | Pause/resume multiple routes |
| POST | /api/routes/bulk-delete | Delete multiple routes |
Route Object
{
"id": "rte_abc123",
"name": "GitHub to Slack",
"description": "Forward GitHub events to Slack",
"sourceId": "src_xyz789",
"destinationIds": ["dst_slack1", "dst_slack2"],
"transformId": "tfm_format123",
"filterId": "flt_pushonly456",
"priority": 10,
"filterConditions": null,
"filterLogic": "AND",
"failoverDestinationIds": ["dst_backup1"],
"failoverAfterAttempts": 3,
"circuitState": "closed",
"circuitCooldownSeconds": 300,
"circuitFailureThreshold": 5,
"circuitProbeSuccessThreshold": 2,
"notifyOnFailure": true,
"notifyOnSuccess": false,
"notifyOnRecovery": true,
"notifyEmails": "[email protected]",
"expectedResponse": null,
"enabled": true,
"source": {
"id": "src_xyz789",
"name": "GitHub"
},
"destinations": [
{"id": "dst_slack1", "name": "Slack #dev"},
{"id": "dst_slack2", "name": "Slack #alerts"}
],
"transform": {
"id": "tfm_format123",
"name": "Slack Formatter"
},
"filter": {
"id": "flt_pushonly456",
"name": "Push Events Only"
},
"eventsRouted": 1523,
"lastEventAt": "2024-01-15T10:30:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}List Routes
GET /api/routesQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number (default: 1) |
| pageSize | number | Items per page (default: 20, max: 100) |
| enabled | boolean | Filter by enabled status |
| sourceId | string | Filter by source |
| destinationId | string | Filter by destination |
Example
curl https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/routes', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const { data, pagination } = await response.json();import requests
response = requests.get(
'https://api.hookbase.app/api/routes',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
result = response.json()
data = result['data']Response
{
"data": [
{
"id": "rte_abc123",
"name": "GitHub to Slack",
"sourceId": "src_xyz789",
"destinationIds": ["dst_slack1"],
"enabled": true,
"eventsRouted": 1523,
"source": {"id": "src_xyz789", "name": "GitHub"},
"destinations": [{"id": "dst_slack1", "name": "Slack #dev"}]
}
],
"pagination": {
"total": 5,
"page": 1,
"pageSize": 20
}
}Create Route
POST /api/routesRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name |
| sourceId | string | Yes | Source ID to listen to |
| destinationIds | string[] | Yes | Array of destination IDs |
| description | string | No | Optional description |
| transformId | string | No | Transform to apply |
| filterId | string | No | Filter to evaluate |
| priority | number | No | Route priority — higher values run first (default: 0) |
| filterConditions | array | No | Inline filter conditions (alternative to filterId). See Filters |
| filterLogic | string | No | Logic for inline filters: AND or OR (default: AND) |
| enabled | boolean | No | Active status (default: true) |
Failover Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| failoverDestinationIds | string[] | [] | Backup destination IDs (max 3). See Failover Guide |
| failoverAfterAttempts | number | 3 | Switch to failover after this many failed attempts |
Circuit Breaker Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| circuitCooldownSeconds | number | 300 | Seconds to wait in open state before probing |
| circuitFailureThreshold | number | 5 | Consecutive failures to trip the circuit |
| circuitProbeSuccessThreshold | number | 2 | Successful probes required to close circuit |
See Circuit Breaker Guide for details.
Notification Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| notifyOnFailure | boolean | false | Send notification when delivery fails |
| notifyOnSuccess | boolean | false | Send notification when delivery succeeds |
| notifyOnRecovery | boolean | false | Send notification when route recovers from failure |
| notifyEmails | string | null | Comma-separated email addresses for notifications |
Response Validation
| Field | Type | Description |
|---|---|---|
| expectedResponse | object | Validate destination response. { "statusCodes": [200, 201], "bodyContains": "ok" } |
Example
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub to Slack",
"sourceId": "src_xyz789",
"destinationIds": ["dst_slack1", "dst_slack2"],
"transformId": "tfm_format123",
"filterId": "flt_pushonly456"
}'const response = await fetch('https://api.hookbase.app/api/routes', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'GitHub to Slack',
sourceId: 'src_xyz789',
destinationIds: ['dst_slack1', 'dst_slack2'],
transformId: 'tfm_format123',
filterId: 'flt_pushonly456'
}),
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/routes',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'name': 'GitHub to Slack',
'sourceId': 'src_xyz789',
'destinationIds': ['dst_slack1', 'dst_slack2'],
'transformId': 'tfm_format123',
'filterId': 'flt_pushonly456'
},
)
data = response.json()Response
{
"id": "rte_new123",
"name": "GitHub to Slack",
"sourceId": "src_xyz789",
"destinationIds": ["dst_slack1", "dst_slack2"],
"transformId": "tfm_format123",
"filterId": "flt_pushonly456",
"enabled": true,
"eventsRouted": 0,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}Get Route
GET /api/routes/{id}Example
curl https://api.hookbase.app/api/routes/rte_abc123 \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/routes/rte_abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data = await response.json();import requests
response = requests.get(
'https://api.hookbase.app/api/routes/rte_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data = response.json()Response
Returns the full route object with expanded source, destinations, transform, and filter.
Update Route
PATCH /api/routes/{id}Request Body
All fields are optional. Only provided fields are updated.
| Field | Type | Description |
|---|---|---|
| name | string | Display name |
| destinationIds | string[] | Array of destination IDs |
| description | string | Optional description |
| transformId | string | Transform ID (null to remove) |
| filterId | string | Filter ID (null to remove) |
| enabled | boolean | Active status |
WARNING
The sourceId cannot be changed after creation. Create a new route instead.
Example
curl -X PATCH https://api.hookbase.app/api/routes/rte_abc123 \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"enabled": false,
"filterId": null
}'const response = await fetch('https://api.hookbase.app/api/routes/rte_abc123', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: false,
filterId: null
}),
});
const data = await response.json();import requests
response = requests.patch(
'https://api.hookbase.app/api/routes/rte_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'enabled': False,
'filterId': None
},
)
data = response.json()Response
Returns the updated route object.
Delete Route
DELETE /api/routes/{id}INFO
Deleting a route does not delete the source, destinations, transform, or filter.
Example
curl -X DELETE https://api.hookbase.app/api/routes/rte_abc123 \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/routes/rte_abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
// Returns 204 No Contentimport requests
response = requests.delete(
'https://api.hookbase.app/api/routes/rte_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
# Returns 204 No ContentResponse
204 No ContentRoute Statistics
Get statistics for a route:
GET /api/routes/{id}/statsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| period | string | Time period: 1h, 24h, 7d, 30d |
Response
{
"eventsRouted": 1523,
"eventsFiltered": 234,
"deliveriesSucceeded": 1489,
"deliveriesFailed": 34,
"avgLatency": 245,
"period": "24h"
}Circuit Breaker Status
Get the current circuit breaker state for a route.
GET /api/routes/{id}/circuit-statusResponse
{
"circuitState": "closed",
"failureCount": 2,
"failureThreshold": 5,
"cooldownSeconds": 300,
"lastFailureAt": "2024-01-15T10:25:00Z",
"lastSuccessAt": "2024-01-15T10:30:00Z",
"openedAt": null,
"probeSuccessCount": 0,
"probeSuccessThreshold": 2
}Circuit States
| State | Description |
|---|---|
closed | Normal operation — deliveries are flowing |
open | Circuit tripped — deliveries are paused, failover activated |
half_open | Probing — sending test deliveries to check recovery |
Reset Circuit Breaker
Manually reset the circuit breaker to closed state.
POST /api/routes/{id}/reset-circuitExample
curl -X POST https://api.hookbase.app/api/routes/rte_abc123/reset-circuit \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/routes/rte_abc123/reset-circuit', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/routes/rte_abc123/reset-circuit',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data = response.json()Response
{
"message": "Circuit breaker reset to closed",
"circuitState": "closed"
}Update Circuit Breaker Config
Update the circuit breaker configuration for a route without modifying other route settings.
PATCH /api/routes/{id}/circuit-configRequest Body
| Field | Type | Description |
|---|---|---|
| circuitCooldownSeconds | number | Seconds to wait before probing |
| circuitFailureThreshold | number | Failures to trip the circuit |
| circuitProbeSuccessThreshold | number | Successful probes to close circuit |
Example
curl -X PATCH https://api.hookbase.app/api/routes/rte_abc123/circuit-config \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"circuitFailureThreshold": 10,
"circuitCooldownSeconds": 600
}'const response = await fetch('https://api.hookbase.app/api/routes/rte_abc123/circuit-config', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
circuitFailureThreshold: 10,
circuitCooldownSeconds: 600
}),
});
const data = await response.json();import requests
response = requests.patch(
'https://api.hookbase.app/api/routes/rte_abc123/circuit-config',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'circuitFailureThreshold': 10,
'circuitCooldownSeconds': 600
},
)
data = response.json()Response
{
"circuitCooldownSeconds": 600,
"circuitFailureThreshold": 10,
"circuitProbeSuccessThreshold": 2,
"message": "Circuit breaker configuration updated"
}Export Routes
Export routes as a portable JSON file. Uses slugs instead of IDs for cross-environment compatibility.
GET /api/routes/exportQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| ids | string | Comma-separated route IDs to export (optional, exports all if omitted) |
Example
# Export all routes
curl https://api.hookbase.app/api/routes/export \
-H "Authorization: Bearer whr_your_api_key"
# Export specific routes
curl "https://api.hookbase.app/api/routes/export?ids=rte_abc,rte_def" \
-H "Authorization: Bearer whr_your_api_key"// Export all routes
const response1 = await fetch('https://api.hookbase.app/api/routes/export', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data1 = await response1.json();
// Export specific routes
const response2 = await fetch('https://api.hookbase.app/api/routes/export?ids=rte_abc,rte_def', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data2 = await response2.json();import requests
# Export all routes
response1 = requests.get(
'https://api.hookbase.app/api/routes/export',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data1 = response1.json()
# Export specific routes
response2 = requests.get(
'https://api.hookbase.app/api/routes/export',
params={'ids': 'rte_abc,rte_def'},
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data2 = response2.json()Response
{
"version": "1.0",
"exportedAt": "2026-01-30T12:00:00Z",
"organizationSlug": "acme-corp",
"routes": [
{
"name": "Stripe to Slack",
"sourceSlug": "stripe-webhooks",
"destinationSlug": "slack-notifications",
"filterSlug": "payment-events",
"transformSlug": null,
"schemaSlug": null,
"filterConditions": null,
"filterLogic": "AND",
"priority": 10,
"isActive": true,
"notifyOnFailure": true,
"notifyOnSuccess": false,
"notifyOnRecovery": true,
"notifyEmails": "[email protected]",
"failureThreshold": 3,
"failoverDestinationSlugs": ["backup-webhook"],
"failoverAfterAttempts": 3,
"circuitCooldownSeconds": 300,
"circuitFailureThreshold": 5,
"circuitProbeSuccessThreshold": 2
}
]
}TIP
Export files use slugs instead of IDs. This makes them portable between environments. Set up matching source and destination slugs in your target environment before importing.
Import Routes
Import routes from a JSON export file.
POST /api/routes/importRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| routes | array | Yes | Array of route objects from export |
| conflictStrategy | string | Yes | How to handle name conflicts: skip, rename, or overwrite |
| validateOnly | boolean | No | If true, validates without creating routes (dry run) |
Conflict Strategies
| Strategy | Behavior |
|---|---|
skip | Keep existing route, don't import the duplicate |
rename | Import with suffix: "My Route" becomes "My Route (1)" |
overwrite | Replace existing route with imported configuration |
Example
# Validate import (dry run)
curl -X POST https://api.hookbase.app/api/routes/import \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routes": [...],
"conflictStrategy": "skip",
"validateOnly": true
}'
# Import routes
curl -X POST https://api.hookbase.app/api/routes/import \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routes": [...],
"conflictStrategy": "rename"
}'// Validate import (dry run)
const response1 = await fetch('https://api.hookbase.app/api/routes/import', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
routes: [...],
conflictStrategy: 'skip',
validateOnly: true
}),
});
const data1 = await response1.json();
// Import routes
const response2 = await fetch('https://api.hookbase.app/api/routes/import', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
routes: [...],
conflictStrategy: 'rename'
}),
});
const data2 = await response2.json();import requests
# Validate import (dry run)
response1 = requests.post(
'https://api.hookbase.app/api/routes/import',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'routes': [...],
'conflictStrategy': 'skip',
'validateOnly': True
},
)
data1 = response1.json()
# Import routes
response2 = requests.post(
'https://api.hookbase.app/api/routes/import',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'routes': [...],
'conflictStrategy': 'rename'
},
)
data2 = response2.json()Response
{
"imported": 3,
"skipped": 1,
"overwritten": 0,
"errors": [],
"warnings": [
"Route 'Legacy Webhook' skipped: already exists"
],
"routes": [
{
"id": "rte_new123",
"name": "Stripe to Slack",
"status": "created"
},
{
"id": "rte_new456",
"name": "GitHub to Discord",
"status": "created"
}
]
}Validation Errors
Before importing, Hookbase validates that all referenced resources exist:
{
"imported": 0,
"skipped": 0,
"errors": [
"Source 'stripe-webhooks' not found",
"Destination 'backup-webhook' not found"
],
"warnings": []
}WARNING
All referenced sources, destinations, filters, transforms, and schemas must exist in the target organization before importing.
Bulk Pause Routes
Pause or resume multiple routes at once.
POST /api/routes/bulk-pauseRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| routeIds | string[] | Yes | Array of route IDs to update |
| isActive | boolean | Yes | Set to false to pause, true to resume |
Example
# Pause multiple routes
curl -X POST https://api.hookbase.app/api/routes/bulk-pause \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routeIds": ["rte_abc123", "rte_def456", "rte_ghi789"],
"isActive": false
}'
# Resume multiple routes
curl -X POST https://api.hookbase.app/api/routes/bulk-pause \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routeIds": ["rte_abc123", "rte_def456"],
"isActive": true
}'// Pause multiple routes
const response1 = await fetch('https://api.hookbase.app/api/routes/bulk-pause', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
routeIds: ['rte_abc123', 'rte_def456', 'rte_ghi789'],
isActive: false
}),
});
const data1 = await response1.json();
// Resume multiple routes
const response2 = await fetch('https://api.hookbase.app/api/routes/bulk-pause', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
routeIds: ['rte_abc123', 'rte_def456'],
isActive: true
}),
});
const data2 = await response2.json();import requests
# Pause multiple routes
response1 = requests.post(
'https://api.hookbase.app/api/routes/bulk-pause',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'routeIds': ['rte_abc123', 'rte_def456', 'rte_ghi789'],
'isActive': False
},
)
data1 = response1.json()
# Resume multiple routes
response2 = requests.post(
'https://api.hookbase.app/api/routes/bulk-pause',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'routeIds': ['rte_abc123', 'rte_def456'],
'isActive': True
},
)
data2 = response2.json()Response
{
"updated": 3,
"routes": [
{ "id": "rte_abc123", "name": "Stripe to Slack", "isActive": false },
{ "id": "rte_def456", "name": "GitHub to Discord", "isActive": false },
{ "id": "rte_ghi789", "name": "Shopify to CRM", "isActive": false }
]
}Bulk Delete Routes
Delete multiple routes at once.
POST /api/routes/bulk-deleteRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| routeIds | string[] | Yes | Array of route IDs to delete |
Example
curl -X POST https://api.hookbase.app/api/routes/bulk-delete \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"routeIds": ["rte_abc123", "rte_def456", "rte_ghi789"]
}'const response = await fetch('https://api.hookbase.app/api/routes/bulk-delete', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
routeIds: ['rte_abc123', 'rte_def456', 'rte_ghi789']
}),
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/routes/bulk-delete',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'routeIds': ['rte_abc123', 'rte_def456', 'rte_ghi789']
},
)
data = response.json()Response
{
"deleted": 3,
"routes": [
{ "id": "rte_abc123", "name": "Stripe to Slack" },
{ "id": "rte_def456", "name": "GitHub to Discord" },
{ "id": "rte_ghi789", "name": "Shopify to CRM" }
]
}DANGER
Bulk delete is irreversible. Consider exporting routes before deleting as a backup.
Error Responses
400 Bad Request
Invalid source or destination:
{
"error": "Bad Request",
"message": "Source with ID src_invalid not found",
"code": "INVALID_SOURCE"
}404 Not Found
Route not found:
{
"error": "Not Found",
"message": "Route with ID rte_xyz not found",
"code": "RESOURCE_NOT_FOUND"
}409 Conflict
Duplicate route:
{
"error": "Conflict",
"message": "A route with this source and destinations already exists",
"code": "DUPLICATE_ROUTE"
}