Skip to content

Sources API

Sources are endpoints that receive incoming webhooks.

Endpoints

MethodPathDescription
GET/api/sourcesList sources
POST/api/sourcesCreate source
GET/api/sources/{id}Get source
PATCH/api/sources/{id}Update source
DELETE/api/sources/{id}Delete source
POST/api/sources/{id}/rotate-secretRotate signing secret
GET/api/sources/exportExport sources as JSON
POST/api/sources/importImport sources from JSON
DELETE/api/sources/bulkBulk delete sources

Source Object

json
{
  "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

http
GET /api/sources

Query Parameters

ParameterTypeDescription
pagenumberPage number (default: 1)
pageSizenumberItems per page (default: 20, max: 100)
enabledbooleanFilter by enabled status
searchstringSearch by name or slug

Example

bash
curl https://api.hookbase.app/api/sources \
  -H "Authorization: Bearer whr_your_api_key"
javascript
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();
python
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

json
{
  "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

http
POST /api/sources

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-safe identifier
descriptionstringNoOptional description
providerstringNoProvider type (see below)
verificationConfigobjectNoSignature verification settings
enabledbooleanNoActive status (default: true)
rejectInvalidSignaturesbooleanNoReject events with invalid signatures (default: false)
rateLimitPerMinutenumberNoMax events per minute (null = unlimited)
ipFilterModestringNoIP filtering mode: none, allowlist, denylist (default: none)
ipAllowliststring[]NoAllowed IP addresses/CIDRs (when mode is allowlist)
ipDenyliststring[]NoDenied IP addresses/CIDRs (when mode is denylist)
encryptFieldsstring[]NoJSONPath expressions for fields to encrypt at rest
maskFieldsstring[]NoJSONPath expressions for fields to mask in logs
dedupEnabledbooleanNoEnable deduplication (default: false)
dedupStrategystringNoDedup strategy (default: auto). See Deduplication
dedupWindowHoursnumberNoDedup window in hours (1-168, default: 24)
dedupCustomHeaderstringNoCustom header for idempotency key strategy

Provider Types

ProviderDescription
githubGitHub webhooks with HMAC-SHA256 verification
stripeStripe webhooks with timestamp + signature
shopifyShopify webhooks with HMAC-SHA256
slackSlack webhooks with signing secret
twilioTwilio webhooks with signature validation
customCustom webhook source with configurable verification
genericGeneric source with no provider-specific behavior

Deduplication Strategies

StrategyDescription
autoAutomatically detect provider-specific event IDs
provider_idUse provider event ID headers (e.g., X-GitHub-Delivery)
payload_hashSHA-256 hash of the payload body
idempotency_keyUse a custom header specified in dedupCustomHeader
noneNo deduplication

Verification Config Options

GitHub

json
{
  "verificationConfig": {
    "type": "github",
    "secret": "your-webhook-secret"
  }
}

Stripe

json
{
  "verificationConfig": {
    "type": "stripe",
    "secret": "whsec_..."
  }
}

Slack

json
{
  "verificationConfig": {
    "type": "slack",
    "secret": "your-signing-secret"
  }
}

Generic HMAC

json
{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-secret",
    "algorithm": "sha256",
    "header": "X-Signature",
    "encoding": "hex",
    "prefix": "sha256="
  }
}

None

json
{
  "verificationConfig": {
    "type": "none"
  }
}

Example

bash
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"
    }
  }'
javascript
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();
python
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

json
{
  "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

http
GET /api/sources/{id}

Example

bash
curl https://api.hookbase.app/api/sources/src_abc123 \
  -H "Authorization: Bearer whr_your_api_key"
javascript
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();
python
import requests

response = requests.get(
    'https://api.hookbase.app/api/sources/src_abc123',
    headers={
        'Authorization': 'Bearer whr_your_api_key',
    },
)
data = response.json()

Response

json
{
  "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

http
PATCH /api/sources/{id}

Request Body

All fields are optional. Only provided fields are updated.

FieldTypeDescription
namestringDisplay name
descriptionstringOptional description
verificationConfigobjectSignature verification settings
enabledbooleanActive status

WARNING

The slug cannot be changed after creation as it's part of the webhook URL.

Example

bash
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
  }'
javascript
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();
python
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

json
{
  "id": "src_abc123",
  "name": "GitHub Production (Updated)",
  "slug": "github",
  "enabled": false,
  "updatedAt": "2024-01-15T11:00:00Z"
}

Delete Source

http
DELETE /api/sources/{id}

DANGER

Deleting a source also deletes all associated events and deliveries.

Example

bash
curl -X DELETE https://api.hookbase.app/api/sources/src_abc123 \
  -H "Authorization: Bearer whr_your_api_key"
javascript
const response = await fetch('https://api.hookbase.app/api/sources/src_abc123', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer whr_your_api_key',
  },
});
// Returns 204 No Content
python
import requests

response = requests.delete(
    'https://api.hookbase.app/api/sources/src_abc123',
    headers={
        'Authorization': 'Bearer whr_your_api_key',
    },
)
# Returns 204 No Content

Response

204 No Content

Rotate Secret

Rotate the signing secret for a source. The old secret is immediately invalidated.

http
POST /api/sources/{id}/rotate-secret

Example

bash
curl -X POST https://api.hookbase.app/api/sources/src_abc123/rotate-secret \
  -H "Authorization: Bearer whr_your_api_key"
javascript
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();
python
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

json
{
  "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.

http
GET /api/sources/export

Query Parameters

ParameterTypeDescription
idsstringComma-separated source IDs to export (optional, exports all if not specified)
includeSensitivebooleanInclude signing secrets in export (default: false)

Example

bash
# 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"
javascript
// 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();
python
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

json
{
  "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.

http
POST /api/sources/import

Request Body

FieldTypeRequiredDescription
sourcesarrayYesArray of source objects to import
conflictStrategystringYesHow to handle slug conflicts: skip, rename, or overwrite
validateOnlybooleanNoIf true, validates without importing (default: false)

Conflict Strategies

StrategyDescription
skipSkip sources that already exist (by slug)
renameAuto-rename conflicting sources (e.g., githubgithub-1)
overwriteUpdate existing sources with imported data

Example

bash
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"
  }'
javascript
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();
python
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:

json
{
  "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

json
{
  "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.

http
DELETE /api/sources/bulk

Request Body

json
{
  "ids": ["src_abc123", "src_def456", "src_ghi789"]
}

Example

bash
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"]}'
javascript
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();
python
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

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

json
{
  "error": "Bad Request",
  "message": "Slug must be lowercase alphanumeric with hyphens",
  "code": "INVALID_SLUG"
}

404 Not Found

Source not found:

json
{
  "error": "Not Found",
  "message": "Source with ID src_xyz not found",
  "code": "RESOURCE_NOT_FOUND"
}

409 Conflict

Duplicate slug:

json
{
  "error": "Conflict",
  "message": "A source with slug 'github' already exists",
  "code": "DUPLICATE_SLUG"
}

Released under the MIT License.