Webhook 403 Forbidden Error — Causes and Fixes
A practical troubleshooting guide for webhook requests that are authenticated but still blocked — covering IP restrictions, permission scopes, and platform-specific configuration.
Quick Answer
A 403 means auth credentials were valid but the request was still denied — usually due to IP allowlist restrictions, insufficient permissions, or a misconfigured endpoint scope.
403 vs 401 — What's the Difference
These two status codes are frequently confused, but they indicate completely different problems:
401 Unauthorized
Authentication failed. The identity of the sender could not be verified — the token was missing, expired, or incorrect. Fix: send valid credentials.
403 Forbidden
Authentication succeeded — the server knows who you are — but that identity is not permitted to access this resource. Fix: change permissions or configuration, not credentials.
If you receive 403, don't waste time rechecking your token value. The credential is fine. The issue is in permissions, IP rules, or resource configuration.
Why Webhooks Return 403
A 403 response from a webhook endpoint usually has one of these root causes:
- The sender's IP address is not on the endpoint's allowlist — common when webhook providers rotate their egress IPs.
- The webhook integration is missing a required permission scope in the platform dashboard (e.g., a Slack bot token without the correct event subscription scope).
- The webhook has been disabled at the platform level, so the provider considers it unauthorized to deliver.
- A subdomain or path mismatch — the endpoint URL was set up for one subdomain but the request arrives on another.
- A Web Application Firewall (WAF) or CDN rule is blocking the request based on user-agent, country, or rate rules before it reaches your application.
- The endpoint requires HTTPS but the request was sent over HTTP and an infrastructure layer is returning 403 instead of redirecting.
How to Debug with Requex
Because a 403 is an authorization problem rather than an authentication problem, the debugging focus shifts from the credentials to the request metadata.
- Open Requex and copy the generated webhook URL.
- Configure the provider to send to the Requex URL — this bypasses any IP allowlists or WAF rules on your endpoint.
- Trigger a test event and confirm that the request arrives in Requex without issue. If it does, your endpoint's infrastructure (WAF, IP rules, firewall) is likely blocking the request.
- Check the
X-Forwarded-Forheader in the captured request to identify the provider's sending IP. - Compare that IP against your endpoint's allowlist to find the missing entry.
- Review the auth headers and scopes that arrived — confirm the token or API key being sent matches what your platform dashboard has configured.
Fix Checklist
- ✓Check if the provider requires IP allowlisting and verify the provider's current egress IP range is included.
- ✓Verify the webhook integration has all required permission scopes in the platform dashboard.
- ✓Confirm the webhook is enabled and active in the provider's settings — a paused or disabled webhook can produce 403.
- ✓Confirm the endpoint URL hasn't changed — some providers allowlist by full URL path, not just domain.
- ✓Test with a different endpoint to rule out a server-side firewall or WAF blocking the specific path.
- ✓Check your CDN or reverse proxy configuration for rules that could match the provider's user-agent or IP range.
Platform-Specific 403 Causes
Different platforms have different reasons for generating a 403 on webhook delivery:
| Platform | Common 403 Cause |
|---|---|
| Stripe | Endpoint not on allowlist; Stripe's IP range changed and the allowlist wasn't updated |
| GitHub | Organization webhook requires admin permissions; token used doesn't have admin:org_hook scope |
| Shopify | App missing a required API scope; webhook topic not enabled in app permissions |
| Slack | Bot token missing the required event subscription scope; app not installed to the workspace |
| Discord | Bot lacks channel permissions; webhook deleted from the channel settings |
IP Allowlisting Example
If your endpoint uses IP allowlisting, always pull the provider's current IP range from their official documentation rather than hardcoding known IPs. Provider IPs change:
# Stripe webhook IP ranges (example — always check Stripe docs for current list)
# https://stripe.com/docs/ips
# nginx example: allow only Stripe IPs
location /webhooks/stripe {
allow 3.18.12.63;
allow 3.130.192.231;
allow 13.235.14.237;
# ... add full list from Stripe docs
deny all;
proxy_pass http://localhost:3000;
}A better approach is to verify the webhook signature instead of allowlisting IPs — it's more resilient to IP changes and doesn't require infrastructure updates when the provider adds new egress addresses.
Related Resources
Webhook 401 Unauthorized
Authentication failed — token, API key, or signature issues
Webhook Security Best Practices
Signature verification, IP rules, and production security
Debug Webhook Errors
General troubleshooting workflow for all webhook error types
Webhook Simulator
Simulate 403 responses to test how your sender handles them
See Exactly What Your Provider Sends
Use Requex to capture the raw request — including the sending IP and all headers — before it hits your protected endpoint.
Open Requex →