Webhook Signature Verifier API
Check webhook signatures of Stripe, GitHub, Slack, Shopify, PayOS and SePay, or any HMAC: see the expected signature, the signed content and replay windows.
Signature mismatches are the most common webhook bug: the body was re-serialised, the wrong secret was used, or the timestamp is outside the tolerance. The webhook verify API checks a payload against the signature scheme of the provider and shows the expected value next to the received one. Supported: Stripe (Stripe-Signature t= / v1=, 5-minute tolerance), GitHub (X-Hub-Signature-256), Slack (v0 with X-Slack-Request-Timestamp), Shopify (base64 X-Shopify-Hmac-Sha256), PayOS (HMAC over the sorted data fields, with the exact signed string) and SePay (Authorization: Apikey).
provider=generic covers everything else: HMAC-SHA1/256/512, hex or base64, optional prefix and the header to read. Send the raw body as payload (a string, exactly as received) and either the headers object or the signature.
Use it to debug integrations and in tests; in production verify signatures in your own code. Secrets are used in memory only and never stored or logged.
Parameters
| Name | In | Type | Required | Default | |
|---|---|---|---|---|---|
provider | body | string | no | generic | stripe, github, slack, shopify, payos, sepay or generic. |
secret | body | string | yes | Signing secret / API key. | |
payload | body | string | yes | Raw request body, as a string. | |
headers | body | object | no | Request headers (the signature header is read from here). | |
signature | body | string | no | Signature value, instead of headers. | |
tolerance | body | number | no | 300 | Max age in seconds (Stripe, Slack); 0 = no check. |
algorithm | body | string | no | sha256 | Generic: sha1, sha256, sha512. |
encoding | body | string | no | hex | Generic: hex or base64. |
Example
curl -X POST "https://apibox.io.vn/v1/webhook-verify" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider":"github","secret":"gh","payload":"{\"id\":\"evt_1\"}","signature":"sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e"}'const res = await fetch('https://apibox.io.vn/v1/webhook-verify', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"provider": "github",
"secret": "gh",
"payload": "{\"id\":\"evt_1\"}",
"signature": "sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e"
}),
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
"https://apibox.io.vn/v1/webhook-verify",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"provider": "github", "secret": "gh", "payload": "{\"id\":\"evt_1\"}", "signature": "sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e"},
timeout=30,
)
print(res.json())<?php
$ch = curl_init('https://apibox.io.vn/v1/webhook-verify');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer YOUR_API_KEY', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"provider":"github","secret":"gh","payload":"{\\"id\\":\\"evt_1\\"}","signature":"sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e"}',
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));Response
{
"valid": true,
"provider": "github",
"expected": "sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e",
"received": "sha256=eb445c690721bdf4fa91d361661d981eb0394f3089b9e9dfb96cddaef63da22e",
"note": "The secret is used in memory only and never stored. …"
}
Use cases
- Debug “invalid signature” errors with Stripe, PayOS, GitHub
- Test webhook endpoints in CI
- Learn how each provider signs webhooks
- Verify payloads captured with the webhook tester
FAQ
Why does my signature not match?
Usually the body was parsed and re-serialised (different spacing or key order). Signatures cover the exact raw bytes; send payload as the original string.
What is the tolerance?
For Stripe and Slack, signatures older than 300 seconds are rejected to prevent replays. Change it with tolerance=, or use now= to test old events.
Is it safe to send my secret?
It is used only to compute the HMAC and never stored. Prefer test-mode secrets, and rotate any secret that leaks.
Related APIs
Hash Generator
Compute MD5, SHA-1, SHA-256, SHA-384, SHA-512 and CRC32 of text or files in one call, as hex or base64, with optional HMAC signing.
B64Encode / Decode
Encode or decode text as Base64, Base64URL, hex, URL encoding, HTML entities, Unicode escapes, ROT13 or binary with one simple API call.
***Password Generator
Generate cryptographically secure random passwords (4–256 chars, up to 100 at once) or check a password’s strength, entropy and estimated crack time.