امضای وبهوک
با اعتبارسنجی امضای وبهوکها مطمئن شوید درخواستها واقعاً از OrcaRail آمدهاند.
چرا امضا را بررسی کنیم؟
امضای وبهوک تضمین میکند که:
- درخواست واقعاً از OrcaRail آمده است
- payload دستکاری نشده است
- درخواستهای مخرب را پردازش نمیکنید
چطور کار میکند
OrcaRail هر درخواست وبهوک را با HMAC SHA-256 و secret وبهوک شما امضا میکند. سرور شما باید پیش از پردازش رویداد این امضا را بررسی کند.
هدر امضا
OrcaRail امضا را در هدر x-webhook-signature قرار میدهد:
x-webhook-signature: abc123def456...
secret وبهوک
secret امضای وبهوک همان secret کلید API شماست (sk_live_...)؛ همان مقداری که هنگام ساخت کلید API دریافت کردید. آن را از طریق PAYMENTS_WEBHOOK_SECRET (یا متغیر محیطی اپ خودتان) تنظیم کنید. امن نگهش دارید و هرگز در کد سمت کلاینت افشایش نکنید.
فرایند بررسی
۱. امضا را بگیرید
امضا را از هدر x-webhook-signature استخراج کنید:
const signature = req.headers['x-webhook-signature']
۲. امضای مورد انتظار را محاسبه کنید
امضای مورد انتظار را با HMAC SHA-256 محاسبه کنید:
const crypto = require('crypto')
const webhookSecret = process.env.PAYMENTS_WEBHOOK_SECRET
// Get the raw request body
const payload = JSON.stringify(req.body)
// Compute HMAC SHA-256
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
۳. امضاها را مقایسه کنید
امضای دریافتی را با امضای مورد انتظار با یک مقایسه زمانثابت مقایسه کنید:
const crypto = require('crypto')
function verifySignature(receivedSignature, expectedSignature) {
if (!receivedSignature || !expectedSignature) {
return false
}
const receivedBuffer = Buffer.from(receivedSignature)
const expectedBuffer = Buffer.from(expectedSignature)
// Use timing-safe comparison to prevent timing attacks
if (receivedBuffer.length !== expectedBuffer.length) {
return false
}
return crypto.timingSafeEqual(receivedBuffer, expectedBuffer)
}
نمونه کامل
Node.js (Express)
const express = require('express')
const crypto = require('crypto')
const app = express()
// Middleware to capture raw body for signature verification
app.use('/webhooks/orcarail', express.raw({ type: 'application/json' }))
app.post('/webhooks/orcarail', (req, res) => {
const webhookSecret = process.env.PAYMENTS_WEBHOOK_SECRET
if (!webhookSecret) {
console.warn(
'PAYMENTS_WEBHOOK_SECRET not set, skipping signature verification'
)
// In production, you should always verify signatures
// return res.status(500).json({ error: 'Webhook secret not configured' });
}
// Get signature from header
const receivedSignature = req.headers['x-webhook-signature']
if (!receivedSignature && webhookSecret) {
return res.status(400).json({ error: 'Missing webhook signature' })
}
// Compute expected signature
const payload = req.body.toString()
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
// Verify signature
if (webhookSecret && !verifySignature(receivedSignature, expectedSignature)) {
return res.status(400).json({ error: 'Invalid webhook signature' })
}
// Parse JSON payload
const event = JSON.parse(payload)
// Process event
handleWebhookEvent(event)
res.status(200).json({ received: true })
})
function verifySignature(receivedSignature, expectedSignature) {
if (!receivedSignature || !expectedSignature) {
return false
}
const receivedBuffer = Buffer.from(receivedSignature)
const expectedBuffer = Buffer.from(expectedSignature)
if (receivedBuffer.length !== expectedBuffer.length) {
return false
}
return crypto.timingSafeEqual(receivedBuffer, expectedBuffer)
}
Python (Flask)
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/orcarail', methods=['POST'])
def webhook():
webhook_secret = os.environ.get('PAYMENTS_WEBHOOK_SECRET')
if not webhook_secret:
return jsonify({'error': 'Webhook secret not configured'}), 500
# Get signature from header
received_signature = request.headers.get('x-webhook-signature')
if not received_signature:
return jsonify({'error': 'Missing webhook signature'}), 400
# Get raw request body
payload = request.get_data()
# Compute expected signature
expected_signature = hmac.new(
webhook_secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
# Verify signature using timing-safe comparison
if not hmac.compare_digest(received_signature, expected_signature):
return jsonify({'error': 'Invalid webhook signature'}), 400
# Parse JSON payload
event = request.get_json()
# Process event
handle_webhook_event(event)
return jsonify({'received': True}), 200
Ruby (Rails)
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def orcarail
webhook_secret = ENV['PAYMENTS_WEBHOOK_SECRET']
unless webhook_secret
return render json: { error: 'Webhook secret not configured' }, status: 500
end
received_signature = request.headers['x-webhook-signature']
unless received_signature
return render json: { error: 'Missing webhook signature' }, status: 400
end
payload = request.body.read
expected_signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'),
webhook_secret,
payload
)
unless ActiveSupport::SecurityUtils.secure_compare(
received_signature,
expected_signature
)
return render json: { error: 'Invalid webhook signature' }, status: 400
end
event = JSON.parse(payload)
handle_webhook_event(event)
render json: { received: true }, status: 200
end
end
PHP
SDK پیاچپی بررسی و تجزیه را در یک فراخوانی انجام میدهد:
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;
$payload = file_get_contents('php://input'); // raw body, not a parsed array
try {
$event = Webhook::constructEvent(
$payload,
$_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '',
getenv('PAYMENTS_WEBHOOK_SECRET'),
);
} catch (SignatureVerificationException $exception) {
http_response_code(400);
echo json_encode(['error' => 'Invalid webhook signature']);
exit;
}
handle_webhook_event($event);
http_response_code(200);
echo json_encode(['received' => true]);
بدون SDK، خودتان HMAC را محاسبه کنید و با hash_equals() مقایسه کنید:
$expected = hash_hmac('sha256', $payload, getenv('PAYMENTS_WEBHOOK_SECRET'));
if (!hash_equals($expected, strtolower($_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''))) {
http_response_code(400);
exit;
}
بهترین روشهای امنیتی
۱. در محیط عملیاتی همیشه امضا را بررسی کنید
در محیط عملیاتی هرگز بررسی امضا را کنار نگذارید، حتی اگر اختیاری باشد:
if (process.env.NODE_ENV === 'production' && !webhookSecret) {
throw new Error('PAYMENTS_WEBHOOK_SECRET must be set in production')
}
۲. از مقایسه زمانثابت استفاده کنید
برای جلوگیری از حملات زمانی همیشه از توابع مقایسه زمانثابت استفاده کنید:
- Node.js:
crypto.timingSafeEqual() - Python:
hmac.compare_digest() - Ruby:
ActiveSupport::SecurityUtils.secure_compare() - PHP:
hash_equals()
۳. secret را امن نگه دارید
- secret وبهوک را در متغیرهای محیطی ذخیره کنید
- هرگز آن را در کنترل نسخه commit نکنید
- بهصورت دورهای عوضش کنید
- برای هر محیط secret جداگانه استفاده کنید
۴. نبود امضا را درست مدیریت کنید
اگر امضا وجود ندارد، درخواست را رد کنید:
if (!receivedSignature) {
return res.status(400).json({ error: 'Missing webhook signature' })
}
تست بدون امضا
برای توسعه محلی میتوانید بررسی امضا را موقتاً غیرفعال کنید:
const webhookSecret = process.env.PAYMENTS_WEBHOOK_SECRET
// Skip verification in development if secret not set
if (!webhookSecret && process.env.NODE_ENV === 'development') {
console.warn('Skipping webhook signature verification in development')
// Process event without verification
} else {
// Verify signature
if (!verifySignature(receivedSignature, expectedSignature)) {
return res.status(400).json({ error: 'Invalid webhook signature' })
}
}
:::warning مهم هرگز در محیط عملیاتی بررسی امضا را غیرفعال نکنید، حتی برای تست. :::
قدمهای بعدی
- رویدادهای وبهوک — با رویدادهای موجود آشنا شوید
- نمای کلی وبهوکها — بهترین روشهای وبهوک را مرور کنید