# Webhook کے دستخط کی جانچ

ادائیگی کی اطلاع ایسے ایڈریس پر آتی ہے جو صرف آپ کا اسٹور اور ہماری سروس جانتے ہیں۔ لیکن ایڈریس لیک ہو سکتا ہے — لاگز سے، کنفیگریشن سے، ڈیولپمنٹ کی ہسٹری سے۔ اصل اطلاع کو جعلی سے الگ پہچاننے کے لیے ہر درخواست پر Webhook کی خفیہ کلید سے دستخط ہوتا ہے۔

آرڈر مکمل کرنے یا اس کا اسٹیٹس بدلنے سے پہلے دستخط جانچیں۔

## دستخط کے ہیڈرز

| ہیڈر        | قدر                                            |
| ------------- | ------------------------------------------------ |
| `X-Timestamp` | بھیجنے کا وقت، سیکنڈز میں unix time                  |
| `X-Signature` | `sha256=` سابقہ اور hex میں HMAC-SHA256  |

دستخط شدہ اسٹرنگ بھیجنے کا وقت، ایک نقطہ اور درخواست کی باڈی ہے:

```
1756901234.{"wallet":{...},"project":{...},"invoice":{...},"payment":{...}}
```

دستخط کی کلید پروجیکٹ کی سیٹنگز میں Webhook کی خفیہ کلید (Webhook secret) فیلڈ ہے۔ اگر قدر افشا ہو جائے، تو آپ اسے وہیں دوبارہ جاری کر سکتے ہیں۔

## جانچ کے مراحل

1. JSON پارس کرنے سے پہلے درخواست کی خام باڈی لیں۔
2. `X-Timestamp` کی قدر، ایک نقطہ اور باڈی کو ملا کر ایک اسٹرنگ بنائیں۔
3. Webhook کی خفیہ کلید سے HMAC-SHA256 کا حساب لگائیں۔
4. نتیجے کا `X-Signature` سے موازنہ کریں، اور اس کے لیے constant-time موازنہ استعمال کریں۔
5. اگر `X-Timestamp` موجودہ وقت سے بہت زیادہ مختلف ہو، تو درخواست رد کر دیں۔

## عمل درآمد کی مثالیں

**PHP**

```php showLineNumbers
<?php
$secret = 'webhook secret from your project settings';

$body      = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

// Guards against a replayed request
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    exit;
}

$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);

// hash_equals compares in constant time
if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    exit;
}

$data = json_decode($body, true);

// Handle the order

http_response_code(200);
```

**TypeScript**

```typescript showLineNumbers
import { createHmac, timingSafeEqual } from 'node:crypto';
import express, { type Request, type Response } from 'express';

const app = express();
const secret = 'webhook secret from your project settings';

// The body must stay raw, so express.raw instead of express.json
app.post('/webhook', express.raw({ type: 'application/json' }), (req: Request, res: Response) => {
    const timestamp = req.get('X-Timestamp') ?? '';
    const signature = req.get('X-Signature') ?? '';
    const body = (req.body as Buffer).toString('utf8');

    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.sendStatus(400);
    }

    const expected = 'sha256=' + createHmac('sha256', secret)
        .update(`${timestamp}.${body}`, 'utf8')
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);

    // Check the length first: timingSafeEqual needs buffers of equal size
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
        return res.sendStatus(403);
    }

    const data = JSON.parse(body);

    // Handle the order

    res.sendStatus(200);
});
```

**JavaScript**

وہی ہینڈلر ٹائپس کے بغیر — سادہ JavaScript پروجیکٹ کے لیے۔

```js showLineNumbers
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();
const secret = 'webhook secret from your project settings';

// The body must stay raw, so express.raw instead of express.json
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const timestamp = req.get('X-Timestamp') ?? '';
    const signature = req.get('X-Signature') ?? '';
    const body = req.body.toString('utf8');

    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.sendStatus(400);
    }

    const expected = 'sha256=' + createHmac('sha256', secret)
        .update(`${timestamp}.${body}`, 'utf8')
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);

    // Check the length first: timingSafeEqual needs buffers of equal size
    if (a.length !== b.length || !timingSafeEqual(a, b)) {
        return res.sendStatus(403);
    }

    const data = JSON.parse(body);

    // Handle the order

    res.sendStatus(200);
});
```

**Python**

```python showLineNumbers
import hmac
import hashlib
import time

from flask import Flask, request

app = Flask(__name__)
secret = 'webhook secret from your project settings'

@app.post('/webhook')
def webhook():
    timestamp = request.headers.get('X-Timestamp', '')
    signature = request.headers.get('X-Signature', '')
    body = request.get_data(as_text=True)

    if abs(time.time() - int(timestamp or 0)) > 300:
        return '', 400

    expected = 'sha256=' + hmac.new(
        secret.encode(),
        f'{timestamp}.{body}'.encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return '', 403

    data = request.get_json()

    # Handle the order

    return '', 200
```

## یاد رکھنے کی باتیں

**دستخط خام باڈی پر ہوتا ہے۔** اگر آپ JSON پارس کر کے اسے دوبارہ بناتے ہیں، تو کلیدوں کی ترتیب اور خالی جگہیں بدل جاتی ہیں — دستخط مماثل نہیں رہتا۔ HMAC کا حساب پارس کرنے سے پہلے لگائیں۔

**ہر دوبارہ کوشش کا اپنا دستخط ہوتا ہے۔** ہر دوبارہ کوشش پر بھیجنے کا وقت نیا ہوتا ہے، اس لیے دستخط بھی مختلف ہوتا ہے۔ آئندہ درخواستوں سے موازنے کے لیے دستخط محفوظ نہ کریں۔

**خفیہ کلید دوبارہ جاری ہو سکتی ہے۔** دوبارہ جاری ہونے کے فوراً بعد پرانی خفیہ کلید کام کرنا بند کر دیتی ہے، اس لیے اسی وقت اپنے اسٹور میں قدر اپ ڈیٹ کریں۔

**دستخط کی جانچ idempotency کا متبادل نہیں۔** دستخط ثابت کرتا ہے کہ درخواست اصلی ہے، یہ نہیں کہ آپ اسے پہلی بار دیکھ رہے ہیں۔ دوبارہ کوشش درست دستخط کے ساتھ آتی ہے — `invoice.id` سے جانچیں۔
