Hookbase
Docs
GuideAPI ReferenceIntegrationsUse CasesCLIMCP
Getting StartedSDK ReferencePortal ComponentsAPI Reference
Get Started

Integrations

OverviewGitHubStripeShopifyPaddleSquareSlackTwilioVercelSupabaseLinearIntercomHubSpotClerkResendSendGridPostmarkCustom Webhooks
DocsReceiveIntegrationsCustom

Custom Webhooks

Integrate any webhook provider using generic HMAC verification or custom headers.

When to Use Custom Integration

Use custom webhooks when:

  • Your provider isn't specifically supported
  • You're building your own webhook sender
  • You need custom signature verification
  • You want to receive webhooks without verification (development only)

Generic HMAC Verification

Most webhook providers use HMAC signatures. Configure generic HMAC verification:

curl -X POST https://api.hookbase.app/api/sources \
  -H "Authorization: Bearer whr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Custom Webhooks",
    "slug": "custom",
    "verificationConfig": {
      "type": "hmac",
      "secret": "your-webhook-secret",
      "algorithm": "sha256",
      "header": "X-Signature",
      "encoding": "hex",
      "prefix": "sha256="
    }
  }'

Configuration Options

OptionDescriptionValues
algorithmHash algorithmsha1, sha256, sha512
headerHeader containing signatureAny header name
encodingSignature encodinghex, base64
prefixPrefix to strip from signaturee.g., sha256=, v1=

Common Provider Configurations

Shopify

{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-shopify-secret",
    "algorithm": "sha256",
    "header": "X-Shopify-Hmac-SHA256",
    "encoding": "base64"
  }
}

Twilio

{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-auth-token",
    "algorithm": "sha1",
    "header": "X-Twilio-Signature",
    "encoding": "base64"
  }
}

SendGrid

{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-verification-key",
    "algorithm": "sha256",
    "header": "X-Twilio-Email-Event-Webhook-Signature",
    "encoding": "base64"
  }
}

Linear

{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-signing-secret",
    "algorithm": "sha256",
    "header": "Linear-Signature",
    "encoding": "hex"
  }
}

Paddle

{
  "verificationConfig": {
    "type": "hmac",
    "secret": "your-webhook-secret",
    "algorithm": "sha256",
    "header": "Paddle-Signature",
    "encoding": "hex",
    "prefix": "h1="
  }
}

No Verification

For development or trusted internal services:

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

Danger

Never use type: none in production! This allows anyone to send webhooks to your endpoint.

API Key Authentication

Some providers authenticate via headers. Add custom headers to your destinations:

{
  "name": "Internal Service",
  "url": "https://internal.example.com/webhook",
  "headers": {
    "X-API-Key": "your-api-key",
    "Authorization": "Bearer your-token"
  }
}

Building Your Own Webhook Sender

Signing Webhooks

When building a system that sends webhooks, implement HMAC signing:

const crypto = require('crypto');
 
function signPayload(payload, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  return 'sha256=' + hmac.digest('hex');
}
 
// Send webhook
const payload = { event: 'user.created', data: { id: '123' } };
const signature = signPayload(payload, 'your-secret');
 
fetch('https://api.hookbase.app/ingest/myorg/custom', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature
  },
  body: JSON.stringify(payload)
});

Python Example

import hmac
import hashlib
import json
import requests
 
def sign_payload(payload, secret):
    message = json.dumps(payload).encode()
    signature = hmac.new(
        secret.encode(),
        message,
        hashlib.sha256
    ).hexdigest()
    return f'sha256={signature}'
 
payload = {'event': 'user.created', 'data': {'id': '123'}}
signature = sign_payload(payload, 'your-secret')
 
requests.post(
    'https://api.hookbase.app/ingest/myorg/custom',
    json=payload,
    headers={
        'X-Signature': signature
    }
)

Transform Examples

Generic Event Normalizer

function transform(payload) {
  // Normalize different webhook formats to a standard structure
  return {
    type: payload.event || payload.type || payload.action || 'unknown',
    data: payload.data || payload.payload || payload,
    timestamp: payload.timestamp || payload.created_at || new Date().toISOString(),
    source: 'custom'
  };
}

Extract Common Fields

function transform(payload) {
  // Extract user info from various formats
  const user = payload.user || payload.data?.user || payload.customer || {};
 
  return {
    event: payload.event,
    userId: user.id || user.user_id || user.uid,
    userEmail: user.email || user.email_address,
    userName: user.name || user.display_name || user.username,
    metadata: payload.metadata || {},
    receivedAt: new Date().toISOString()
  };
}

Webhook to Slack

function transform(payload) {
  return {
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: `*Webhook Received*\n\`\`\`${JSON.stringify(payload, null, 2)}\`\`\``
        }
      }
    ]
  };
}

Filter Examples

By Event Type

{
  "name": "User Events",
  "logic": "and",
  "conditions": [
    {
      "field": "event",
      "operator": "starts_with",
      "value": "user."
    }
  ]
}

By Environment

{
  "name": "Production Only",
  "logic": "and",
  "conditions": [
    {
      "field": "environment",
      "operator": "equals",
      "value": "production"
    }
  ]
}

Has Required Fields

{
  "name": "Valid Payload",
  "logic": "and",
  "conditions": [
    {
      "field": "event",
      "operator": "exists",
      "value": ""
    },
    {
      "field": "data.id",
      "operator": "exists",
      "value": ""
    }
  ]
}

Testing Custom Webhooks

Using cURL

# Without signature (for testing with verification disabled)
curl -X POST https://api.hookbase.app/ingest/myorg/custom \
  -H "Content-Type: application/json" \
  -d '{"event": "test", "data": {"message": "Hello!"}}'
 
# With HMAC signature
PAYLOAD='{"event": "test", "data": {"message": "Hello!"}}'
SECRET="your-secret"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print "sha256="$2}')
 
curl -X POST https://api.hookbase.app/ingest/myorg/custom \
  -H "Content-Type: application/json" \
  -H "X-Signature: $SIGNATURE" \
  -d "$PAYLOAD"

Using the Dashboard

  1. Navigate to Testing
  2. Select your custom source
  3. Enter a test payload
  4. Click Send Test

Troubleshooting

Signature Verification Failed

  1. Verify the algorithm matches your provider's implementation
  2. Check the header name is correct (case-sensitive)
  3. Ensure encoding matches (hex vs base64)
  4. Verify the prefix is stripped correctly
  5. Check if the payload is being stringified consistently

Wrong Signature Format

Common issues:

  • Using base64 when provider uses hex (or vice versa)
  • Missing or wrong prefix
  • Wrong hash algorithm
  • Extra whitespace in the signature

Payload Not Matching

Ensure you're signing the exact same bytes that are sent:

  • Same JSON serialization (key order, spacing)
  • Same character encoding (UTF-8)
  • No modifications by proxies or middleware
PreviousPostmark

On this page

When to Use Custom IntegrationGeneric HMAC VerificationConfiguration OptionsCommon Provider ConfigurationsNo VerificationAPI Key AuthenticationBuilding Your Own Webhook SenderSigning WebhooksPython ExampleTransform ExamplesGeneric Event NormalizerExtract Common FieldsWebhook to SlackFilter ExamplesBy Event TypeBy EnvironmentHas Required FieldsTesting Custom WebhooksUsing cURLUsing the DashboardTroubleshootingSignature Verification FailedWrong Signature FormatPayload Not Matching