Webhook İmza Doğrulama

Webhook isteklerinin güvenliğini sağlamak için imza doğrulama nasıl yapılır

Genel Bakış

Wespoke, gönderdiği tüm webhook isteklerini bir HMAC-SHA256 imzası ile imzalar.Bu imzayı doğrulayarak webhook'un gerçekten Wespoke platformundan geldiğinden emin olabilirsiniz.

Önemli: Webhook imza doğrulamasını uygulamanız şiddetle önerilir.Bu, kötü niyetli kullanıcıların sahte webhook istekleri göndermesini engeller.

İmza Header'ı

Her webhook isteği <code className="bg-secondary px-2 py-1 rounded">X-Wespoke-Signature</code> header'ı içerir.Bu header, isteğin gövdesinin HMAC-SHA256 hash'ini içerir.

Örnek Header
POST /webhook HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Wespoke-Signature: sha256=1234567890abcdef...
X-Wespoke-Timestamp: 1696774496789

{
  "event": "call.started",
  "data": {...}
}

İmza Hesaplama

İmza, webhook secret'ınız kullanılarak şu şekilde hesaplanır:

HMAC-SHA256(secret, timestamp + "." + request_body)

Webhook secret'ınızı kontrol panelinde <strong>Ayarlar → Webhooks</strong> bölümünden alabilirsiniz.

Node.js Örneği

Express.js ile İmza Doğrulama
const crypto = require('crypto');
const express = require('express');
const app = express();

// To get the raw body
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

function verifyWebhookSignature(req, secret) {
  const signature = req.headers['x-wespoke-signature'];
  const timestamp = req.headers['x-wespoke-timestamp'];

  if (!signature || !timestamp) {
    return false;
  }

  // Timestamp check (reject requests older than 5 minutes)
  const currentTime = Date.now();
  if (Math.abs(currentTime - parseInt(timestamp)) > 5 * 60 * 1000) {
    return false;
  }

  // Signature calculation
  const payload = `${timestamp}.${req.rawBody}`;
  const expectedSignature = 'sha256=' +
    crypto
      .createHmac('sha256', secret)
      .update(payload)
      .digest('hex');

  // Secure comparison against timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', (req, res) => {
  const webhookSecret = process.env.WESPOKE_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req, webhookSecret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Signature verified, process webhook
  const { event, data } = req.body;

  // Process the event...
  console.log(`Webhook received: ${event}`);

  res.status(200).json({ received: true });
});

app.listen(3000);

Python Örneği

Flask ile İmza Doğrulama
import hmac
import hashlib
import time
import os
import { getTranslations } from 'next-intl/server';
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_webhook_signature(request, secret):
    signature = request.headers.get('X-Wespoke-Signature')
    timestamp = request.headers.get('X-Wespoke-Timestamp')

    if not signature or not timestamp:
        return False

    # Timestamp check (reject requests older than 5 minutes)
    current_time = int(time.time() * 1000)
    if abs(current_time - int(timestamp)) > 5 * 60 * 1000:
        return False

    # Signature calculation
    payload = f"{timestamp}.{request.get_data(as_text=True)}"
    expected_signature = 'sha256=' + hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # Secure comparison against timing attacks
    return hmac.compare_digest(signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    webhook_secret = os.environ.get('WESPOKE_WEBHOOK_SECRET')

    if not verify_webhook_signature(request, webhook_secret):
        return jsonify({'error': 'Invalid signature'}), 401

    # Signature verified, process webhook
    data = request.json
    event = data.get('event')

    # Process the event...
    print(f"Webhook received: {event}")

    return jsonify({'received': True}), 200

if __name__ == '__main__':
    app.run(port=3000)

PHP Örneği

PHP ile İmza Doğrulama
<?php

function verifyWebhookSignature($secret) {
    $signature = $_SERVER['HTTP_X_WESPOKE_SIGNATURE'] ?? '';
    $timestamp = $_SERVER['HTTP_X_WESPOKE_TIMESTAMP'] ?? '';

    if (empty($signature) || empty($timestamp)) {
        return false;
    }

    // Timestamp check (reject requests older than 5 minutes)
    $currentTime = round(microtime(true) * 1000);
    if (abs($currentTime - intval($timestamp)) > 5 * 60 * 1000) {
        return false;
    }

    // Signature calculation
    $rawBody = file_get_contents('php://input');
    $payload = $timestamp . '.' . $rawBody;
    $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);

    // Secure comparison against timing attacks
    return hash_equals($signature, $expectedSignature);
}

// Webhook endpoint
$webhookSecret = getenv('WESPOKE_WEBHOOK_SECRET');

if (!verifyWebhookSignature($webhookSecret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Signature verified, process webhook
$data = json_decode(file_get_contents('php://input'), true);
$event = $data['event'] ?? '';

// Process the event...
error_log("Webhook received: " . $event);

http_response_code(200);
echo json_encode(['received' => true]);

Güvenlik En İyi Uygulamaları

  • Her zaman webhook imzasını doğrulayın
  • Zaman damgası kontrolü yaparak replay attack'ları önleyin
  • Webhook secret'ınızı güvenli bir şekilde saklayın (ortam değişkenleri kullanın)
  • HTTPS kullanarak webhook endpoint'inizi güvenli hale getirin
  • Timing-safe karşılaştırma fonksiyonları kullanın
  • Webhook secret'ınızı periyodik olarak yenileyin

Test Etme

Webhook imza doğrulamanızı test etmek için kontrol panelinde bulunan webhook test aracını kullanabilirsiniz.Bu araç, gerçek bir webhook isteği simüle eder ve imza doğrulamanızın doğru çalışıp çalışmadığını kontrol etmenizi sağlar.

Webhook Test Aracına Git