Authorization and Security
Every webhook sent by Prembly includes two critical security headers:
- x-prembly-signature: HMAC-SHA256 (base64 encoded)
- token: This is a unique webhook identifier
You Must verify the signature before processing any webhook.
Why Verify Webhooks?
without verification, anyone could send the webhooks to your endpoint. Signature verification ensures:
- The webhook came from Prembly
- The payload hasn't been tampered with
- Protection against replay attacks (when combined with token tracking)
How it Works
- Prembly generates signature:
- HMAC-SHA256(payload, your_public_key)
- Base64 encodes it
- Sends as an x-prembly-signature header
- You Verify:
- Complete the same signature using your public key
- Compare with the received signature
- If they match=webhook is authentic
Implementation Examples
Python/Django
import hmac
import hashlib
import base64
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
def verify_prembly_signature(payload, signature_b64, public_key):
"""Verify webhook signature."""
expected_signature = hmac.new(
public_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).digest()
expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')
return hmac.compare_digest(signature_b64, expected_signature_b64)
@csrf_exempt
def webhook_endpoint(request):
# Extract headers
signature = request.headers.get('x-prembly-signature')
token = request.headers.get('token')
if not signature or not token:
return JsonResponse({'error': 'Missing security headers'}, status=401)
# Get raw body
payload = request.body.decode('utf-8')
# Verify signature
public_key = "YOUR_PREMBLY_PUBLIC_KEY" # From your dashboard
if not verify_prembly_signature(payload, signature, public_key):
return JsonResponse({'error': 'Invalid signature'}, status=401)
# Parse and process webhook
data = json.loads(payload)
# Your business logic here
session_id = data.get('data', {}).get('widget_info', {}).get('session_id')
status = data.get('status')
verification_result = data.get('data', {}).get('verification_response', {})
# Return 200 to acknowledge receipt
return JsonResponse({'status': 'received'}, status=200)
Python/Flask
from flask import Flask, request, jsonify
import hmac
import hashlib
import base64
import json
app = Flask(name)
@app.route('/webhook', methods=['POST'])
def webhook():
# Extract headers
signature = request.headers.get('x-prembly-signature')
token = request.headers.get('token')
if not signature or not token:
return jsonify({'error': 'Missing security headers'}), 401
# Get raw body
payload = request.get_data(as_text=True)
# Verify signature
public_key = "YOUR_PREMBLY_PUBLIC_KEY"
expected_signature = hmac.new(
public_key.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).digest()
expected_signature_b64 = base64.b64encode(expected_signature).decode('utf-8')
if not hmac.compare_digest(signature, expected_signature_b64):
return jsonify({'error': 'Invalid signature'}), 401
# Process webhook
data = json.loads(payload)
# Your business logic
return jsonify({'status': 'received'}), 200
Node.js/Express
const express = require('express');
const crypto = require('crypto');
const app = express();
// Important: Get raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
function verifySignature(payload, signature, publicKey) {
const hmac = crypto.createHmac('sha256', publicKey);
hmac.update(payload);
const expectedSignature = hmac.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-prembly-signature'];
const token = req.headers['token'];
if (!signature || !token) {
return res.status(401).json({ error: 'Missing security headers' });
}
const publicKey = process.env.PREMBLY_PUBLIC_KEY;
if (!verifySignature(req.rawBody, signature, publicKey)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const { data, status } = req.body;
// Your business logic
res.status(200).json({ status: 'received' });
});
PHP
'Missing security headers']); exit; } // Verify signature $publicKey = 'YOUR_PREMBLY_PUBLIC_KEY'; if (!verifyPremblySignature($payload, $signature, $publicKey)) { http_response_code(401); echo json_encode(['error' => 'Invalid signature']); exit; } // Process webhook $data = json_decode($payload, true); // Your business logic http_response_code(200); echo json_encode(['status' => 'received']); # 1. Always Use HTTPS **Never expose webhook endpoints over HTTP**. Use HTTPS to encrypt data in transit. # 2. Implement Idempotency Store the token value to prevent duplicate processing: # Check if token already processed if WebhookLog.objects.filter(token=token).exists(): return JsonResponse({'status': 'already_processed'}, status=200) # Process webhook # ... # Save token WebhookLog.objects.create(token=token, processed_at=timezone.now()) # 3. Validate Webhook Structure After verifying the signature, validate the payload structure: required_fields = ['status', 'data'] if not all(field in data for field in required_fields): return JsonResponse({'error': 'Invalid payload structure'}, status=400) # 4. Handle Errors Gracefully Return appropriate HTTP status codes: 200: Successfully processed 401: Invalid signature 400: Invalid payload 500: Server error (Prembly may retry) # 5. Log Everything Log all webhook events for debugging: logger.info(f"Webhook received - Token: {token}, Session: {session_id}") # 6. Respond Quickly Process webhooks asynchronously to avoid timeouts:# Queue for background processing webhook_task.delay(data) # Respond immediately return JsonResponse({'status': 'queued'}, status=200)
# Troubleshooting "Invalid signature" error **"Invalid signature"** error **Cause**: Signature mismatch **Solutions:** Verify you're using the correct public key from your Prembly dashboard Ensure you're using the raw request body (not parsed JSON) Check character encoding (UTF-8) Verify no modifications to the request body before verification Testing signature verification Use this Python script to test: # Testing signature verification **Use this Python script to test:** import hmac import hashlib import base64 payload = '{"status":"completed","data":{"session_id":"123"}}' public_key = "your_public_key" signature = hmac.new( public_key.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256 ).digest() signature_b64 = base64.b64encode(signature).decode('utf-8') print(f"Expected signature: {signature_b64}")
Updated 5 days ago
