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:
bash
curl -X POST https://api.webhookrelay.com/api/organizations/{orgId}/sources \
-H "Authorization: Bearer YOUR_TOKEN" \
-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
| Option | Description | Values |
|---|---|---|
algorithm | Hash algorithm | sha1, sha256, sha512 |
header | Header containing signature | Any header name |
encoding | Signature encoding | hex, base64 |
prefix | Prefix to strip from signature | e.g., sha256=, v1= |
Common Provider Configurations
Shopify
json
{
"verificationConfig": {
"type": "hmac",
"secret": "your-shopify-secret",
"algorithm": "sha256",
"header": "X-Shopify-Hmac-SHA256",
"encoding": "base64"
}
}Twilio
json
{
"verificationConfig": {
"type": "hmac",
"secret": "your-auth-token",
"algorithm": "sha1",
"header": "X-Twilio-Signature",
"encoding": "base64"
}
}SendGrid
json
{
"verificationConfig": {
"type": "hmac",
"secret": "your-verification-key",
"algorithm": "sha256",
"header": "X-Twilio-Email-Event-Webhook-Signature",
"encoding": "base64"
}
}Linear
json
{
"verificationConfig": {
"type": "hmac",
"secret": "your-signing-secret",
"algorithm": "sha256",
"header": "Linear-Signature",
"encoding": "hex"
}
}Paddle
json
{
"verificationConfig": {
"type": "hmac",
"secret": "your-webhook-secret",
"algorithm": "sha256",
"header": "Paddle-Signature",
"encoding": "hex",
"prefix": "h1="
}
}No Verification
For development or trusted internal services:
json
{
"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:
json
{
"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:
javascript
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.webhookrelay.com/ingest/myorg/custom', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Signature': signature
},
body: JSON.stringify(payload)
});Python Example
python
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.webhookrelay.com/ingest/myorg/custom',
json=payload,
headers={
'X-Signature': signature
}
)Transform Examples
Generic Event Normalizer
javascript
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
javascript
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
javascript
function transform(payload) {
return {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Webhook Received*\n\`\`\`${JSON.stringify(payload, null, 2)}\`\`\``
}
}
]
};
}Filter Examples
By Event Type
json
{
"name": "User Events",
"logic": "and",
"conditions": [
{
"field": "event",
"operator": "starts_with",
"value": "user."
}
]
}By Environment
json
{
"name": "Production Only",
"logic": "and",
"conditions": [
{
"field": "environment",
"operator": "equals",
"value": "production"
}
]
}Has Required Fields
json
{
"name": "Valid Payload",
"logic": "and",
"conditions": [
{
"field": "event",
"operator": "exists",
"value": ""
},
{
"field": "data.id",
"operator": "exists",
"value": ""
}
]
}Testing Custom Webhooks
Using cURL
bash
# Without signature (for testing with verification disabled)
curl -X POST https://api.webhookrelay.com/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.webhookrelay.com/ingest/myorg/custom \
-H "Content-Type: application/json" \
-H "X-Signature: $SIGNATURE" \
-d "$PAYLOAD"Using the Dashboard
- Navigate to Testing
- Select your custom source
- Enter a test payload
- Click Send Test
Troubleshooting
Signature Verification Failed
- Verify the algorithm matches your provider's implementation
- Check the header name is correct (case-sensitive)
- Ensure encoding matches (hex vs base64)
- Verify the prefix is stripped correctly
- 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