Dokumentation

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

EventDescription
otp.sentOTP has been sent to the carrier/provider
otp.deliveredOTP was successfully delivered to the recipient
otp.failedOTP delivery failed
otp.verifiedOTP was successfully verified
otp.expiredOTP has expired without being verified
otp.max_attemptsMaximum verification attempts reached

Setting Up Webhooks

1. Configure Endpoint

In your K-OTP dashboard:

  1. Go to Settings > Webhooks
  2. Click "Add Webhook"
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Configure security settings
  6. 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 CodeAction
200-299Success, no retry
300-399Redirect, follow and retry
400-499Client error, no retry (except 408, 429)
500-599Server error, retry with backoff
TimeoutNetwork 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:

  1. Go to Settings > Webhooks
  2. Click "Test" next to your webhook
  3. Select event type to simulate
  4. 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

  1. Check webhook URL is accessible
  2. Verify SSL certificate is valid
  3. Ensure proper HTTP method (POST)
  4. Check firewall and IP restrictions

Signature Verification Failing

  1. Verify webhook secret is correct
  2. Check timestamp tolerance
  3. Ensure payload is not modified
  4. Use raw body, not parsed JSON

High Retry Rate

  1. Optimize endpoint response time
  2. Return proper HTTP status codes
  3. Handle errors gracefully
  4. 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