Webhook Authentication — Complete Developer Guide
How to protect your webhook endpoints with Bearer tokens, API keys, Basic Auth, and HMAC signatures — plus how to test each method without deploying server code.
Why Webhook Endpoints Need Authentication
A webhook endpoint is a publicly accessible URL — by definition reachable from the internet. Without authentication, anyone who discovers your URL can send arbitrary POST requests to it, potentially triggering business logic, polluting your event queue, or causing unintended side effects.
Webhook authentication solves two distinct problems. The first is authorization: only trusted callers (your partners, your own services, or specific platforms) should be able to deliver events. The second is integrity: the payload hasn't been tampered with in transit. Different authentication methods address these problems in different ways, and choosing the right one depends on whether you control both sides of the integration.
This guide covers the four most common webhook authentication patterns, when to use each, and how to test them using Requex.me's built-in auth testing feature — without writing or running a backend server.
1. Bearer Token Authentication
Bearer token authentication is the most widely-used method for machine-to-machine webhook delivery. The caller includes a secret token in the Authorization header:
POST https://api.requex.me/hook/your-id HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{"event": "order.created", "data": {...}}The token value is typically either a random opaque string (a pre-shared secret) or a signed JWT (JSON Web Token). For webhook-to-server integrations, opaque secrets are simpler and almost always sufficient — the server just does a constant-time string comparison.
Node.js verification example
const crypto = require('crypto');
const EXPECTED_TOKEN = process.env.WEBHOOK_SECRET;
function verifyBearerToken(req, res, next) {
const header = req.headers['authorization'] || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : '';
// Use timingSafeEqual to prevent timing attacks
const expected = Buffer.from(EXPECTED_TOKEN);
const actual = Buffer.from(token);
if (actual.length !== expected.length ||
!crypto.timingSafeEqual(actual, expected)) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}💡 Best for: Internal service-to-service webhooks, custom integrations where you control the sender, and any scenario where the webhook source is a system you own.
2. API Key Authentication
API key authentication is similar to Bearer tokens, but the key is passed in a custom header (such as X-API-Key) or as a query parameter rather than the Authorization header. This is common when the webhook sender is a SaaS platform that has its own API key concept.
POST https://api.requex.me/hook/your-id HTTP/1.1
X-API-Key: sk_live_abc123xyz
Content-Type: application/json
{"event": "payment.success", "amount": 9900}You configure the header name and expected value in your webhook receiver. Some platforms also support embedding the key in the webhook URL itself as a query parameter (e.g., /hook/your-id?api_key=abc123), though header-based transmission is preferred since query parameters can leak into server logs.
⚠️ Note: API keys don't verify payload integrity — a valid key confirms who sent the request, but not that the body hasn't been modified. Combine with HMAC signatures for high-security scenarios.
💡 Best for: Receiving webhooks from external platforms (billing tools, CRMs, automation platforms) that let you configure a custom API key or secret header value.
3. Basic Authentication
HTTP Basic Authentication encodes a username and password pair in the Authorization header as a Base64-encoded string. While older than the other methods, many legacy systems and some well-known platforms (like certain Shopify flows) still use it for webhook delivery.
# The header value is: Basic base64("username:password")
POST https://api.requex.me/hook/your-id HTTP/1.1
Authorization: Basic d2ViaG9vazpteXNlY3JldA==
Content-Type: application/jsonVerifying Basic Auth in Express
function verifyBasicAuth(req, res, next) {
const header = req.headers['authorization'] || '';
if (!header.startsWith('Basic ')) {
res.setHeader('WWW-Authenticate', 'Basic realm="Webhooks"');
return res.status(401).json({ error: 'Unauthorized' });
}
const decoded = Buffer.from(header.slice(6), 'base64').toString();
const colonIdx = decoded.indexOf(':');
const username = decoded.slice(0, colonIdx);
const password = decoded.slice(colonIdx + 1);
if (username !== process.env.WH_USER ||
password !== process.env.WH_PASS) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}💡 Best for: Legacy integrations, platforms that only support Basic Auth, or internal tools where you want the simplest possible auth that every HTTP client supports.
4. HMAC Signature Verification
HMAC (Hash-based Message Authentication Code) is the gold standard for webhook authentication because it solves both problems simultaneously: it proves the sender knows the shared secret, and it guarantees the payload hasn't been modified in transit. Stripe, GitHub, Shopify, and most serious platforms use HMAC-SHA256.
The sender computes a signature by applying HMAC-SHA256 to the raw request body using a shared secret, then includes the signature in a header. Your receiver performs the same computation and compares the two:
POST https://api.requex.me/hook/your-id HTTP/1.1
X-Webhook-Signature: sha256=c8b4d2e1f3a65d7b9c20e4f...
Content-Type: application/json
{"event": "push", "repository": "my-repo", "commits": [...]}Verification (Node.js)
const crypto = require('crypto');
function verifyHmacSignature(rawBody, signatureHeader, secret) {
// Compute expected signature
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody) // must be the raw bytes, not parsed JSON
.digest('hex');
// Constant-time comparison prevents timing attacks
const sigBuffer = Buffer.from(signatureHeader);
const expBuffer = Buffer.from(expected);
if (sigBuffer.length !== expBuffer.length) return false;
return crypto.timingSafeEqual(sigBuffer, expBuffer);
}
// In your Express handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
if (!verifyHmacSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
// Process payload...
res.sendStatus(200);
});⚠️ Critical: You must compute the HMAC over the raw request body bytes, not over a re-serialized JavaScript object. Any JSON serialization differences (key ordering, whitespace) will cause verification failures. Always buffer the raw body before parsing.
💡 Best for: Any production webhook endpoint where security matters. HMAC is the recommended method when you control the sender, and the only correct method when receiving from platforms like Stripe or GitHub that mandate it.
Comparison: When to Use Each Method
| Method | Proves Identity | Proves Integrity | Best Use Case |
|---|---|---|---|
| Bearer Token | ✅ Yes | ❌ No | Internal services, trusted senders |
| API Key | ✅ Yes | ❌ No | SaaS platform integrations with custom headers |
| Basic Auth | ✅ Yes | ❌ No | Legacy systems, maximum compatibility |
| HMAC Signature | ✅ Yes | ✅ Yes | Production APIs, Stripe/GitHub/Shopify, any high-security scenario |
Testing Webhook Authentication with Requex.me
Requex.me lets you configure authentication requirements on your test webhook endpoint and then send test requests to verify your implementation — no server code required. This is useful for:
- Verifying that your sender correctly attaches the expected auth header before going to production.
- Testing rejection behavior — confirming that requests with wrong or missing credentials receive a
401 Unauthorizedresponse. - Simulating the WWW-Authenticate challenge flow for Basic Auth.
- Validating HMAC signature computation logic by sending a real signed request and checking whether it passes.
Step 1: Open Response Settings
Visit Requex.me to get your unique webhook URL. Click the Response Settings button in the left panel to open the configuration modal.
Step 2: Configure the Auth Tab
Navigate to the Auth tab in the settings modal. Toggle Require Authentication on, then select your method:
Bearer Token
Enter the expected token value. Requex.me will reject any request whose Authorization: Bearer <token> header doesn't match.
API Key
Specify the header name (e.g. X-API-Key) and the expected key value. All other requests are rejected with 401.
Basic Auth
Set the expected username and password. Enable Simulate Challenge to return a WWW-Authenticate header on 401 responses, just like a real server.
HMAC Signature
Enter your shared secret. Requex.me will compute HMAC-SHA256 over the raw request body and compare it to the signature in the header, exactly as a production server would.
Step 3: Save and Test
Click Save to persist your auth configuration. Your endpoint now actively enforces authentication. Send a request with the correct credentials and you'll see it appear in the request log. Send one without — or with a wrong token — and you'll get a 401 with no entry in the log.
# Test with correct Bearer token — should appear in dashboard
curl -X POST https://api.requex.me/hook/your-id \
-H "Authorization: Bearer my-secret-token" \
-H "Content-Type: application/json" \
-d '{"event":"test"}'
# Test with wrong token — should return 401 and not appear in dashboard
curl -X POST https://api.requex.me/hook/your-id \
-H "Authorization: Bearer wrong-token" \
-H "Content-Type: application/json" \
-d '{"event":"test"}'Common Webhook Auth Problems and Fixes
Always getting 401 even with correct credentials
Cause: Token has trailing whitespace or newline characters
Fix: Trim the token value before comparison: token.trim()
HMAC signature never matches
Cause: Computing HMAC over parsed JSON instead of raw body
Fix: Buffer the raw request body before any JSON parsing. In Express, use express.raw() before express.json().
Basic Auth header missing
Cause: Client sends credentials in URL (https://user:pass@host)
Fix: Configure your HTTP client to send the Authorization header, not URL credentials.
Auth passes but request looks tampered
Cause: Using API key / Bearer token (identity only, not integrity)
Fix: Switch to HMAC to verify both sender identity and payload integrity.
Timing attack vulnerability
Cause: Using === for token comparison
Fix: Always use crypto.timingSafeEqual() for constant-time comparison.
Webhook Authentication Checklist
- ✓Endpoint served over HTTPS only (never plain HTTP)
- ✓Authentication method chosen based on security requirements
- ✓Secrets stored in environment variables, never hardcoded
- ✓Constant-time comparison used for token/signature matching
- ✓HMAC computed over raw request body, not parsed JSON
- ✓Timestamp validation implemented (for HMAC with replay protection)
- ✓Idempotency checks in place using event IDs
- ✓Auth tested with valid AND invalid credentials before deploying
Test Your Webhook Auth Now
Configure Bearer, API Key, Basic Auth, or HMAC on your free Requex.me endpoint and validate your integration in seconds.
Open Webhook Tester →Related Guides & Resources
Webhook Security Best Practices
HTTPS, signatures, rate limiting, and production-ready security
Complete Webhook Testing Guide
End-to-end testing workflow for any webhook integration
Debug Webhook Errors
Fix 400, 401, 403, 500 errors in webhook handlers
Stripe Webhook Testing
Verify Stripe's HMAC signatures in development
GitHub Webhook Testing
Capture and verify GitHub's signed webhook events
Webhook Inspector Tool
Inspect auth headers and payloads in real-time