Sources API
Sources are endpoints that receive incoming webhooks.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/sources | List sources |
| POST | /api/sources | Create source |
| GET | /api/sources/{id} | Get source |
| PATCH | /api/sources/{id} | Update source |
| DELETE | /api/sources/{id} | Delete source |
| POST | /api/sources/{id}/rotate-secret | Rotate signing secret |
| GET | /api/sources/export | Export sources as JSON |
| POST | /api/sources/import | Import sources from JSON |
| DELETE | /api/sources/bulk | Bulk delete sources |
Source Object
{
"id": "src_abc123",
"name": "GitHub Webhooks",
"slug": "github",
"description": "Production GitHub webhooks",
"provider": "github",
"webhookUrl": "https://api.hookbase.app/ingest/myorg/github",
"verificationConfig": {
"type": "github",
"secret": "********"
},
"rejectInvalidSignatures": true,
"rateLimitPerMinute": 1000,
"ipFilterMode": "allowlist",
"ipAllowlist": ["192.168.1.0/24", "10.0.0.1"],
"ipDenylist": [],
"encryptFields": ["$.payment.card_number"],
"maskFields": ["$.user.email"],
"dedupEnabled": true,
"dedupStrategy": "provider_id",
"dedupWindowHours": 24,
"dedupCustomHeader": null,
"enabled": true,
"eventsCount": 1523,
"lastEventAt": "2024-01-15T10:30:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}List Sources
GET /api/sourcesQuery 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 |
| search | string | Search by name or slug |
Example
curl https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/sources', {
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/sources',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
result = response.json()
data = result['data']Response
{
"data": [
{
"id": "src_abc123",
"name": "GitHub Webhooks",
"slug": "github",
"webhookUrl": "https://api.hookbase.app/ingest/myorg/github",
"enabled": true,
"eventsCount": 1523
}
],
"pagination": {
"total": 5,
"page": 1,
"pageSize": 20
}
}Create Source
POST /api/sourcesRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name |
| slug | string | Yes | URL-safe identifier |
| description | string | No | Optional description |
| provider | string | No | Provider type (see below) |
| verificationConfig | object | No | Signature verification settings |
| enabled | boolean | No | Active status (default: true) |
| rejectInvalidSignatures | boolean | No | Reject events with invalid signatures (default: false) |
| rateLimitPerMinute | number | No | Max events per minute (null = unlimited) |
| ipFilterMode | string | No | IP filtering mode: none, allowlist, denylist (default: none) |
| ipAllowlist | string[] | No | Allowed IP addresses/CIDRs (when mode is allowlist) |
| ipDenylist | string[] | No | Denied IP addresses/CIDRs (when mode is denylist) |
| encryptFields | string[] | No | JSONPath expressions for fields to encrypt at rest |
| maskFields | string[] | No | JSONPath expressions for fields to mask in logs |
| dedupEnabled | boolean | No | Enable deduplication (default: false) |
| dedupStrategy | string | No | Dedup strategy (default: auto). See Deduplication |
| dedupWindowHours | number | No | Dedup window in hours (1-168, default: 24) |
| dedupCustomHeader | string | No | Custom header for idempotency key strategy |
Provider Types
| Provider | Description |
|---|---|
github | GitHub webhooks with HMAC-SHA256 verification |
stripe | Stripe webhooks with timestamp + signature |
shopify | Shopify webhooks with HMAC-SHA256 |
slack | Slack webhooks with signing secret |
twilio | Twilio webhooks with signature validation |
custom | Custom webhook source with configurable verification |
generic | Generic source with no provider-specific behavior |
Deduplication Strategies
| Strategy | Description |
|---|---|
auto | Automatically detect provider-specific event IDs |
provider_id | Use provider event ID headers (e.g., X-GitHub-Delivery) |
payload_hash | SHA-256 hash of the payload body |
idempotency_key | Use a custom header specified in dedupCustomHeader |
none | No deduplication |
Verification Config Options
GitHub
{
"verificationConfig": {
"type": "github",
"secret": "your-webhook-secret"
}
}Stripe
{
"verificationConfig": {
"type": "stripe",
"secret": "whsec_..."
}
}Slack
{
"verificationConfig": {
"type": "slack",
"secret": "your-signing-secret"
}
}Generic HMAC
{
"verificationConfig": {
"type": "hmac",
"secret": "your-secret",
"algorithm": "sha256",
"header": "X-Signature",
"encoding": "hex",
"prefix": "sha256="
}
}None
{
"verificationConfig": {
"type": "none"
}
}Example
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Production",
"slug": "github-prod",
"description": "Production GitHub webhooks",
"verificationConfig": {
"type": "github",
"secret": "my-secret-key"
}
}'const response = await fetch('https://api.hookbase.app/api/sources', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'GitHub Production',
slug: 'github-prod',
description: 'Production GitHub webhooks',
verificationConfig: {
type: 'github',
secret: 'my-secret-key'
}
}),
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/sources',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'name': 'GitHub Production',
'slug': 'github-prod',
'description': 'Production GitHub webhooks',
'verificationConfig': {
'type': 'github',
'secret': 'my-secret-key'
}
},
)
data = response.json()Response
{
"id": "src_def456",
"name": "GitHub Production",
"slug": "github-prod",
"description": "Production GitHub webhooks",
"webhookUrl": "https://api.hookbase.app/ingest/myorg/github-prod",
"verificationConfig": {
"type": "github"
},
"enabled": true,
"eventsCount": 0,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}Get Source
GET /api/sources/{id}Example
curl https://api.hookbase.app/api/sources/src_abc123 \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/sources/src_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/sources/src_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data = response.json()Response
{
"id": "src_abc123",
"name": "GitHub Webhooks",
"slug": "github",
"description": "Production GitHub webhooks",
"webhookUrl": "https://api.hookbase.app/ingest/myorg/github",
"verificationConfig": {
"type": "github"
},
"enabled": true,
"eventsCount": 1523,
"lastEventAt": "2024-01-15T10:30:00Z",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}Update Source
PATCH /api/sources/{id}Request Body
All fields are optional. Only provided fields are updated.
| Field | Type | Description |
|---|---|---|
| name | string | Display name |
| description | string | Optional description |
| verificationConfig | object | Signature verification settings |
| enabled | boolean | Active status |
WARNING
The slug cannot be changed after creation as it's part of the webhook URL.
Example
curl -X PATCH https://api.hookbase.app/api/sources/src_abc123 \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Production (Updated)",
"enabled": false
}'const response = await fetch('https://api.hookbase.app/api/sources/src_abc123', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'GitHub Production (Updated)',
enabled: false
}),
});
const data = await response.json();import requests
response = requests.patch(
'https://api.hookbase.app/api/sources/src_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'name': 'GitHub Production (Updated)',
'enabled': False
},
)
data = response.json()Response
{
"id": "src_abc123",
"name": "GitHub Production (Updated)",
"slug": "github",
"enabled": false,
"updatedAt": "2024-01-15T11:00:00Z"
}Delete Source
DELETE /api/sources/{id}DANGER
Deleting a source also deletes all associated events and deliveries.
Example
curl -X DELETE https://api.hookbase.app/api/sources/src_abc123 \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/sources/src_abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
// Returns 204 No Contentimport requests
response = requests.delete(
'https://api.hookbase.app/api/sources/src_abc123',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
# Returns 204 No ContentResponse
204 No ContentRotate Secret
Rotate the signing secret for a source. The old secret is immediately invalidated.
POST /api/sources/{id}/rotate-secretExample
curl -X POST https://api.hookbase.app/api/sources/src_abc123/rotate-secret \
-H "Authorization: Bearer whr_your_api_key"const response = await fetch('https://api.hookbase.app/api/sources/src_abc123/rotate-secret', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/sources/src_abc123/rotate-secret',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data = response.json()Response
{
"id": "src_abc123",
"verificationConfig": {
"type": "github",
"secret": "new-generated-secret-value"
},
"message": "Secret rotated successfully"
}WARNING
The new secret is only shown once in the response. Update your webhook provider with the new secret immediately.
Export Sources
Export sources as a JSON file for backup or migration to another organization.
GET /api/sources/exportQuery Parameters
| Parameter | Type | Description |
|---|---|---|
| ids | string | Comma-separated source IDs to export (optional, exports all if not specified) |
| includeSensitive | boolean | Include signing secrets in export (default: false) |
Example
# Export all sources (secrets redacted)
curl https://api.hookbase.app/api/sources/export \
-H "Authorization: Bearer whr_your_api_key"
# Export specific sources with secrets
curl "https://api.hookbase.app/api/sources/export?ids=src_1,src_2&includeSensitive=true" \
-H "Authorization: Bearer whr_your_api_key"// Export all sources (secrets redacted)
const response1 = await fetch('https://api.hookbase.app/api/sources/export', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data1 = await response1.json();
// Export specific sources with secrets
const response2 = await fetch('https://api.hookbase.app/api/sources/export?ids=src_1,src_2&includeSensitive=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer whr_your_api_key',
},
});
const data2 = await response2.json();import requests
# Export all sources (secrets redacted)
response1 = requests.get(
'https://api.hookbase.app/api/sources/export',
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data1 = response1.json()
# Export specific sources with secrets
response2 = requests.get(
'https://api.hookbase.app/api/sources/export',
params={
'ids': 'src_1,src_2',
'includeSensitive': 'true'
},
headers={
'Authorization': 'Bearer whr_your_api_key',
},
)
data2 = response2.json()Response
{
"version": "1.0",
"exportedAt": "2024-01-15T10:30:00Z",
"organizationSlug": "myorg",
"sources": [
{
"name": "GitHub Webhooks",
"slug": "github",
"provider": "github",
"description": "Production GitHub webhooks",
"signingSecret": "***REDACTED***",
"rejectInvalidSignatures": true,
"rateLimitPerMinute": null,
"ipFilterMode": "none",
"ipAllowlist": [],
"ipDenylist": [],
"encryptFields": [],
"maskFields": [],
"dedupEnabled": false,
"dedupStrategy": "auto",
"dedupWindowHours": 24,
"dedupCustomHeader": null,
"isActive": true
}
]
}TIP
When includeSensitive is false (default), signing secrets are replaced with ***REDACTED***. A new secret will be generated when importing.
Import Sources
Import sources from a JSON export file.
POST /api/sources/importRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| sources | array | Yes | Array of source objects to import |
| conflictStrategy | string | Yes | How to handle slug conflicts: skip, rename, or overwrite |
| validateOnly | boolean | No | If true, validates without importing (default: false) |
Conflict Strategies
| Strategy | Description |
|---|---|
skip | Skip sources that already exist (by slug) |
rename | Auto-rename conflicting sources (e.g., github → github-1) |
overwrite | Update existing sources with imported data |
Example
curl -X POST https://api.hookbase.app/api/sources/import \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"sources": [
{
"name": "Stripe Payments",
"slug": "stripe",
"provider": "stripe",
"rejectInvalidSignatures": true,
"isActive": true
}
],
"conflictStrategy": "skip"
}'const response = await fetch('https://api.hookbase.app/api/sources/import', {
method: 'POST',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
sources: [
{
name: 'Stripe Payments',
slug: 'stripe',
provider: 'stripe',
rejectInvalidSignatures: true,
isActive: true
}
],
conflictStrategy: 'skip'
}),
});
const data = await response.json();import requests
response = requests.post(
'https://api.hookbase.app/api/sources/import',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'sources': [
{
'name': 'Stripe Payments',
'slug': 'stripe',
'provider': 'stripe',
'rejectInvalidSignatures': True,
'isActive': True
}
],
'conflictStrategy': 'skip'
},
)
data = response.json()Validation Response
When validateOnly: true:
{
"valid": true,
"validationResults": [
{
"index": 0,
"name": "Stripe Payments",
"slug": "stripe",
"status": "valid",
"errors": [],
"warnings": ["Signing secret is redacted - a new secret will be generated on import"]
}
],
"summary": {
"total": 1,
"toCreate": 1,
"toOverwrite": 0,
"toSkip": 0,
"errors": 0
}
}Import Response
{
"success": true,
"summary": {
"imported": 3,
"skipped": 1,
"overwritten": 0,
"failed": 0
},
"details": {
"imported": ["Stripe Payments", "GitHub Webhooks", "Slack Events"],
"skipped": ["Internal API"],
"overwritten": [],
"failed": []
}
}Bulk Delete Sources
Delete multiple sources in a single request.
DELETE /api/sources/bulkRequest Body
{
"ids": ["src_abc123", "src_def456", "src_ghi789"]
}Example
curl -X DELETE https://api.hookbase.app/api/sources/bulk \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"ids": ["src_abc123", "src_def456"]}'const response = await fetch('https://api.hookbase.app/api/sources/bulk', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
ids: ['src_abc123', 'src_def456']
}),
});
const data = await response.json();import requests
response = requests.delete(
'https://api.hookbase.app/api/sources/bulk',
headers={
'Authorization': 'Bearer whr_your_api_key',
'Content-Type': 'application/json',
},
json={
'ids': ['src_abc123', 'src_def456']
},
)
data = response.json()Response
{
"success": true,
"deleted": 2
}DANGER
Deleting sources also deletes all associated events and deliveries. This action cannot be undone.
Error Responses
400 Bad Request
Invalid input data:
{
"error": "Bad Request",
"message": "Slug must be lowercase alphanumeric with hyphens",
"code": "INVALID_SLUG"
}404 Not Found
Source not found:
{
"error": "Not Found",
"message": "Source with ID src_xyz not found",
"code": "RESOURCE_NOT_FOUND"
}409 Conflict
Duplicate slug:
{
"error": "Conflict",
"message": "A source with slug 'github' already exists",
"code": "DUPLICATE_SLUG"
}