Authorizations
Webhook authentication for Bridge event notifications sent to your CRM.
Authentication Process:
- Configure a webhook API key in your Bridge dashboard
- Bridge will include this key in the
X-Bridge-API-Key
header - Bridge will sign the request using HMAC-SHA256 and include the signature in
X-Bridge-Signature
header - Bridge will include a timestamp in
X-Bridge-Timestamp
header
Signature Construction:
- Create signature payload:
{TIMESTAMP}{REQUEST_BODY}
(timestamp + raw body, no separator) - Generate HMAC-SHA256 signature using your webhook secret key
- Encode signature as hexadecimal (lowercase)
- Send as
X-Bridge-Signature: sha256={hex_signature}
Security Features:
- Replay Attack Prevention: Timestamp verification (reject requests older than 5 minutes)
- Message Integrity: HMAC-SHA256 ensures payload hasn't been tampered with
- Authentication: Verifies the request originated from Bridge
- Timing Attack Mitigation: Use constant-time comparison for signature verification
Example Headers from Bridge:
X-Bridge-API-Key: wh_1234567890abcdef
X-Bridge-Timestamp: 1642234567
X-Bridge-Signature: sha256=a1b2c3d4e5f6789012345678901234567890abcdef123456789012345678901234
Verification Steps:
- Extract timestamp from
X-Bridge-Timestamp
header - Verify timestamp is within 5 minutes of current time (prevents replay attacks)
- Reconstruct signature payload:
timestamp + request_body
- Generate expected signature using HMAC-SHA256 with your webhook secret
- Compare signatures using constant-time comparison
Verification Code Example (Node.js):
const crypto = require('crypto');
function verifyBridgeWebhook(body, signature, timestamp, secret) {
// 1. Check timestamp (reject if older than 5 minutes)
const currentTime = Math.floor(Date.now() / 1000);
const requestTime = parseInt(timestamp);
if (currentTime - requestTime > 300) { // 300 seconds = 5 minutes
throw new Error('Request timestamp too old');
}
// 2. Construct signature payload (timestamp + body, no separator)
const signaturePayload = timestamp + body;
// 3. Generate expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signaturePayload, 'utf8')
.digest('hex');
// 4. Extract received signature (remove 'sha256=' prefix)
const receivedSignature = signature.replace('sha256=', '');
// 5. Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
// Usage example
app.post('/webhooks/bridge-events', (req, res) => {
const signature = req.headers['x-bridge-signature'];
const timestamp = req.headers['x-bridge-timestamp'];
const apiKey = req.headers['x-bridge-api-key'];
const body = req.body; // raw body as string
try {
// Verify API key
if (apiKey !== process.env.BRIDGE_WEBHOOK_API_KEY) {
return res.status(401).send('Invalid API key');
}
// Verify signature
if (!verifyBridgeWebhook(body, signature, timestamp, process.env.BRIDGE_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
console.log('Verified webhook received:', JSON.parse(body));
res.status(200).json({ status: 'received' });
} catch (error) {
console.error('Webhook verification failed:', error.message);
res.status(401).send('Verification failed');
}
});
Python Example:
import hmac
import hashlib
import time
def verify_bridge_webhook(body: str, signature: str, timestamp: str, secret: str) -> bool:
# Check timestamp (reject if older than 5 minutes)
current_time = int(time.time())
request_time = int(timestamp)
if current_time - request_time > 300:
raise ValueError("Request timestamp too old")
# Construct signature payload
signature_payload = timestamp + body
# Generate expected signature
expected_signature = hmac.new(
secret.encode('utf-8'),
signature_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Extract received signature
received_signature = signature.replace('sha256=', '')
# Constant-time comparison
return hmac.compare_digest(expected_signature, received_signature)
Body
Unique identifier for the event (used for idempotency)
"evt_123456789"
Type of event that occurred
user.daily_resume_generated
, task.created
, task.updated
, task.completed
, opportunity.created
, opportunity.updated
, conversation.created
, conversation.updated
, conversation.participant_added
, conversation.participant_removed
, conversation.status_changed
"user.daily_resume_generated"
The entity type that the event relates to
user
, task
, opportunity
, conversation
"user"
Unique identifier of the workspace where the event occurred
"ws_abc123"
ISO 8601 timestamp when the event occurred
"2024-01-15T10:30:00Z"
Event-specific data structure
{
"userId": "user_abc123",
"summary": "Daily resume containing 5 completed tasks",
"conversationId": "conv_abc123",
"participantType": "external",
"participantIdentifier": "john.doe@example.com"
}
Response
Event received successfully
"received"