> For the complete documentation index, see [llms.txt](https://docs.aviowiki.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aviowiki.com/webhooks/verifying-signatures.md).

# Verifying signatures

Every webhook delivery includes an `Aviowiki-Signature` header that you should use to verify the request is genuinely from aviowiki.

The header format is:

```
Aviowiki-Signature: t=1715782200000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9
```

| Part | Description                                                     |
| ---- | --------------------------------------------------------------- |
| `t`  | Unix timestamp in milliseconds when the signature was generated |
| `v1` | HMAC-SHA256 signature in lowercase hex                          |

### Verification Steps

1. Extract the timestamp (`t`) and signature (`v1`) from the header
2. Construct the signed message: `{timestamp}.{raw_request_body}`
3. Compute HMAC-SHA256 of the message using your subscription's `secret` as the key
4. Compare your computed signature with `v1`
5. Optionally, check that the timestamp is within an acceptable tolerance (e.g. 5 minutes) to prevent replay attacks

{% tabs %}
{% tab title="Python" %}

```python
import hmac
import hashlib
import time

def verify_signature(payload, signature_header, secret, tolerance_ms=300000):
    parts = dict(item.split("=", 1) for item in signature_header.split(","))
    timestamp = int(parts["t"])
    expected_sig = parts["v1"]

    # Check timestamp tolerance (5 minutes)
    if time.time() * 1000 - timestamp > tolerance_ms:
        return False

    message = f"{timestamp}.{payload}"
    computed = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(computed, expected_sig)
```

{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public boolean verifySignature(String payload, String signatureHeader,
                               String secret, long toleranceMs) {
    String[] parts = signatureHeader.split(",");
    long timestamp = Long.parseLong(parts[0].split("=")[1]);
    String expectedSig = parts[1].split("=")[1];

    if (System.currentTimeMillis() - timestamp > toleranceMs) {
        return false;
    }

    String message = timestamp + "." + payload;
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(
        secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));

    StringBuilder hex = new StringBuilder();
    for (byte b : hash) {
        hex.append(String.format("%02x", b));
    }

    return hex.toString().equals(expectedSig);
}
```

{% endtab %}
{% endtabs %}
