Webhooks
Set up real-time notifications for OTP events using K-OTP webhooks.
Overview
Webhooks allow you to receive real-time notifications when OTP events occur. Instead of polling our API, K-OTP will send HTTP POST requests to your specified endpoint when events happen.
Webhook Events
Available Events
Event | Description |
---|---|
otp.sent | OTP has been sent to the carrier/provider |
otp.delivered | OTP was successfully delivered to the recipient |
otp.failed | OTP delivery failed |
otp.verified | OTP was successfully verified |
otp.expired | OTP has expired without being verified |
otp.max_attempts | Maximum verification attempts reached |
Setting Up Webhooks
1. Configure Endpoint
In your K-OTP dashboard:
- Go to Settings > Webhooks
- Click "Add Webhook"
- Enter your endpoint URL
- Select events to subscribe to
- Configure security settings
- Test the webhook
2. Implement Webhook Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
// Use raw body parser for signature verification
app.use('/webhook', express.raw({type: 'application/json'}));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-kotp-signature'];
const timestamp = req.headers['x-kotp-timestamp'];
// Verify signature (recommended)
if (!verifySignature(req.body, signature, timestamp)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body);
// Handle the event
switch (event.type) {
case 'otp.delivered':
handleOTPDelivered(event.data);
break;
case 'otp.verified':
handleOTPVerified(event.data);
break;
case 'otp.failed':
handleOTPFailed(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('OK');
});
function verifySignature(payload, signature, timestamp) {
const secret = process.env.KOTP_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
Webhook Payload Format
Event Structure
{
"id": "evt_1234567890abcdef",
"type": "otp.delivered",
"created": 1642248600,
"data": {
"id": "otp_1234567890abcdef",
"to": "+1234567890",
"method": "sms",
"type": "verification",
"status": "delivered",
"delivered_at": "2024-01-15T10:30:15Z",
"carrier": "Verizon",
"country": "US",
"cost": {
"amount": 0.05,
"currency": "USD"
},
"metadata": {
"user_id": "user_123",
"action": "login"
}
}
}
Event Types and Payloads
otp.sent
{
"type": "otp.sent",
"data": {
"id": "otp_1234567890abcdef",
"to": "+1234567890",
"method": "sms",
"type": "verification",
"status": "sent",
"sent_at": "2024-01-15T10:30:00Z"
}
}
otp.delivered
{
"type": "otp.delivered",
"data": {
"id": "otp_1234567890abcdef",
"status": "delivered",
"delivered_at": "2024-01-15T10:30:15Z",
"carrier": "Verizon",
"country": "US"
}
}
otp.verified
{
"type": "otp.verified",
"data": {
"id": "otp_1234567890abcdef",
"status": "verified",
"verified_at": "2024-01-15T10:32:00Z",
"attempts": 1
}
}
otp.failed
{
"type": "otp.failed",
"data": {
"id": "otp_1234567890abcdef",
"status": "failed",
"failed_at": "2024-01-15T10:30:30Z",
"error": {
"code": "CARRIER_REJECTED",
"message": "Invalid phone number"
}
}
}
Security
Signature Verification
Always verify webhook signatures to ensure requests come from K-OTP:
import hmac
import hashlib
import time
def verify_webhook_signature(payload, signature, timestamp, secret):
# Check timestamp to prevent replay attacks
if abs(time.time() - int(timestamp)) > 300: # 5 minutes
return False
# Create expected signature
message = f"{timestamp}.{payload}"
expected_signature = hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
# Compare signatures securely
return hmac.compare_digest(signature, expected_signature)
# Usage
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-KOTP-Signature')
timestamp = request.headers.get('X-KOTP-Timestamp')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
return 'Unauthorized', 401
# Process webhook
return 'OK', 200
IP Allowlisting
Restrict webhook access to K-OTP IP addresses:
# K-OTP webhook IP ranges
203.0.113.0/24
198.51.100.0/24
2001:db8::/32
Retry Logic
Automatic Retries
K-OTP automatically retries failed webhook deliveries:
- Initial attempt: Immediate
- Retry 1: After 5 seconds
- Retry 2: After 30 seconds
- Retry 3: After 5 minutes
- Retry 4: After 30 minutes
- Final retry: After 2 hours
Exponential Backoff
// Example retry implementation
async function sendWebhook(url, payload, attempt = 1) {
const maxAttempts = 5;
const baseDelay = 1000; // 1 second
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
timeout: 10000 // 10 seconds
});
if (response.status >= 200 && response.status < 300) {
return response;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (attempt >= maxAttempts) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
return sendWebhook(url, payload, attempt + 1);
}
}
Error Handling
Response Codes
K-OTP expects specific HTTP status codes:
Status Code | Action |
---|---|
200-299 | Success, no retry |
300-399 | Redirect, follow and retry |
400-499 | Client error, no retry (except 408, 429) |
500-599 | Server error, retry with backoff |
Timeout | Network timeout, retry |
Best Practices
app.post('/webhook', async (req, res) => {
try {
// Process webhook quickly
const event = req.body;
// For long-running tasks, use a queue
await messageQueue.add('process-otp-event', event);
// Respond quickly
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
// Return 500 for retries
res.status(500).send('Internal Server Error');
}
});
Testing Webhooks
Webhook Testing Tool
Use our testing tool in the dashboard:
- Go to Settings > Webhooks
- Click "Test" next to your webhook
- Select event type to simulate
- Review request/response details
Local Development
For local testing, use tools like ngrok:
# Install ngrok
npm install -g ngrok
# Expose local server
ngrok http 3000
# Use the ngrok URL in webhook settings
# https://abc123.ngrok.io/webhook
Webhook Debugging
// Log all webhook events for debugging
app.post('/webhook', (req, res) => {
console.log('Webhook received:', {
headers: req.headers,
body: req.body,
timestamp: new Date().toISOString()
});
res.status(200).send('OK');
});
Advanced Features
Event Filtering
Filter events by specific criteria:
{
"url": "https://your-app.com/webhook",
"events": ["otp.delivered", "otp.verified"],
"filters": {
"method": ["sms"],
"type": ["verification", "two_factor"],
"metadata.user_type": ["premium"]
}
}
Custom Headers
Add custom headers to webhook requests:
{
"url": "https://your-app.com/webhook",
"headers": {
"X-API-Version": "v1",
"X-Source": "k-otp"
}
}
Webhook Transformation
Transform webhook payloads before sending:
{
"url": "https://your-app.com/webhook",
"transform": {
"template": {
"event_type": "{{type}}",
"otp_id": "{{data.id}}",
"status": "{{data.status}}",
"custom_field": "{{data.metadata.user_id}}"
}
}
}
Monitoring and Analytics
Webhook Logs
View detailed webhook delivery logs:
- Request/response details
- Delivery status and timing
- Error messages and retry attempts
- Performance metrics and trends
Webhook Analytics
Track webhook performance:
// Example webhook metrics
{
"webhooks": {
"total_sent": 10000,
"success_rate": 99.5,
"average_response_time": 150,
"retry_rate": 2.1,
"error_breakdown": {
"timeout": 15,
"4xx_errors": 5,
"5xx_errors": 30
}
}
}
Troubleshooting
Common Issues
Webhook Not Receiving Events
- Check webhook URL is accessible
- Verify SSL certificate is valid
- Ensure proper HTTP method (POST)
- Check firewall and IP restrictions
Signature Verification Failing
- Verify webhook secret is correct
- Check timestamp tolerance
- Ensure payload is not modified
- Use raw body, not parsed JSON
High Retry Rate
- Optimize endpoint response time
- Return proper HTTP status codes
- Handle errors gracefully
- Implement proper logging
Debug Webhook Issues
# Test webhook endpoint manually
curl -X POST https://your-app.com/webhook \
-H "Content-Type: application/json" \
-H "X-KOTP-Signature: test_signature" \
-H "X-KOTP-Timestamp: 1642248600" \
-d '{"type":"otp.delivered","data":{"id":"test"}}'
Best Practices
Performance
- Respond quickly (< 5 seconds)
- Use async processing for heavy tasks
- Implement idempotency for duplicate events
- Scale horizontally for high volume
Reliability
- Handle retries gracefully
- Log webhook events for debugging
- Monitor webhook health and alerts
- Have fallback mechanisms for failures
Security
- Always verify signatures
- Use HTTPS endpoints only
- Implement rate limiting
- Validate webhook payload structure