> For the complete documentation index, see [llms.txt](https://developers.kwikpaisa.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.kwikpaisa.com/v3-guide/webhooks/signature-verification.md).

# Signature verification

KwikPaisa signs every webhook request using HMAC SHA256 to ensure webhook authenticity and payload integrity.

Webhook signature verification helps merchants:

* Verify webhook authenticity
* Prevent unauthorized requests
* Detect payload tampering
* Protect webhook endpoints against replay attacks

All webhook requests should always be verified before processing transaction data.

***

## Webhook Headers

Every webhook request contains:

```http
Content-Type: application/json
X-SIGNATURE: GENERATED_SIGNATURE
X-TIMESTAMP: GENERATED_TIMESTAMP
```

## Signature Formula

Refer to: \\

➡️ [Signature Generation](/v3-guide/authentication/signature.md)

## Verification Process

To verify a webhook request:

1. Read the raw webhook payload
2. Extract `X-SIGNATURE` header
3. Extract `X-TIMESTAMP` header
4. Generate local HMAC SHA256 signature
5. Compare generated signature with received signature
6. Process webhook only if signatures match

***

## Important Rules

* Use the raw request payload exactly as received
* Do not modify JSON formatting before verification
* Verify timestamps to prevent replay attacks
* Reject invalid signatures immediately
* Always use your server-side `secret_key`

***

## Example Webhook Payload

```
{
  "event": "payment.success",
  "data": {
    "order_id": "6116229263036",
    "amount": "105.00",
    "currency": "INR",
    "status": "PAID"
  }
}
```

***

{% tabs %}
{% tab title="Node.js" %}

```javascript
const crypto = require('crypto');
const payload = JSON.stringify(req.body);
const timestamp = req.headers['x-timestamp'];
const receivedSignature = req.headers['x-signature'];
const generatedSignature = crypto
  .createHmac('sha256', 'YOUR_SECRET_KEY')
  .update(payload + timestamp)
  .digest('hex');
if (generatedSignature === receivedSignature) {
  console.log('Webhook verified successfully');
} else {
  console.log('Invalid webhook signature');
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
$receivedSignature = $_SERVER['HTTP_X_SIGNATURE'];
$generatedSignature = hash_hmac(
    'sha256',
    $payload . $timestamp,
    'YOUR_SECRET_KEY'
);
if ($generatedSignature === $receivedSignature) {
    echo "Webhook verified successfully";
} else {
    echo "Invalid webhook signature";
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hmac
import hashlib
payload = request.data.decode()
timestamp = request.headers.get('X-TIMESTAMP')
received_signature = request.headers.get('X-SIGNATURE')
generated_signature = hmac.new(
    b'YOUR_SECRET_KEY',
    (payload + timestamp).encode(),
    hashlib.sha256
).hexdigest()
if generated_signature == received_signature:
    print("Webhook verified successfully")
else:
    print("Invalid webhook signature")
```

{% endtab %}

{% tab title="Java" %}

```
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
String payload = requestBody;
String timestamp = request.getHeader("X-TIMESTAMP");
String receivedSignature = request.getHeader("X-SIGNATURE");
String data = payload + timestamp;
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
    "YOUR_SECRET_KEY".getBytes(),
    "HmacSHA256"
);
sha256Hmac.init(secretKey);
byte[] hash = sha256Hmac.doFinal(data.getBytes());
StringBuilder generatedSignature = new StringBuilder();
for (byte b : hash) {
    generatedSignature.append(
        String.format("%02x", b)
    );
}
if (generatedSignature.toString().equals(receivedSignature)) {
    System.out.println("Webhook verified successfully");
} else {
    System.out.println("Invalid webhook signature");
}
```

{% endtab %}
{% endtabs %}

## Common Verification Failures

### Invalid Signature

Possible reasons:

* Incorrect secret key
* Modified payload
* Incorrect JSON formatting
* Wrong timestamp
* Incorrect signature generation logic

***

### Expired Timestamp

Possible reasons:

* Delayed webhook processing
* System time mismatch
* Replay attack prevention triggered

***

## Security Recommendations

* Always verify webhook signatures
* Use HTTPS webhook endpoints only
* Reject invalid requests immediately
* Store webhook logs securely
* Verify timestamps before processing
* Never expose secret keys publicly

***

## Replay Attack Protection

KwikPaisa includes timestamps in webhook requests to prevent replay attacks.

Replay attacks occur when attackers resend previously valid webhook payloads maliciously.

To prevent replay attacks:

* Validate timestamps
* Reject old requests
* Store processed webhook event IDs
* Implement idempotent processing

***

## Recommended Production Workflow

1. Receive webhook request
2. Read raw payload
3. Extract headers
4. Verify signature
5. Validate timestamp
6. Store webhook event
7. Verify transaction using APIs
8. Update internal systems
9. Return HTTP 200 response

***

## Best Practices

* Process webhooks asynchronously
* Avoid long-running webhook responses
* Return HTTP 200 quickly
* Verify payment/payout status using APIs
* Maintain webhook audit logs


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers.kwikpaisa.com/v3-guide/webhooks/signature-verification.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
