All Webhook Errors

Webhook 405 Method Not Allowed — Causes & Fixes

A webhook 405 Method Not Allowed response means your route exists but is registered for a different HTTP method than what the provider sent. Webhooks are almost universally POST; a 405 means your route is configured for GET, PUT, or anything else.

Root Causes

1. Route registered with the wrong verb

Most common: a typo or copy-paste from a non-webhook route. app.get('/webhook') instead of app.post('/webhook'). Webhooks are POST; if you see 405, this is your first check.

2. CORS preflight (OPTIONS) without a handler

Cross-origin webhook deliveries (rare, but happens with some browser-based provider integrations) start with an OPTIONS preflight. If your route is registered for POST only and your framework doesn't auto-handle OPTIONS, the preflight returns 405. Add an OPTIONS handler that returns 200 with the right CORS headers, or use middleware like cors.

3. Reverse proxy stripping the body

Caddy, nginx, and Apache can be configured to disallow specific methods on specific paths. Check your reverse proxy config: is there a method POST filter that's actually denying POST? Usually a copy-paste-then-flip mistake.

4. Framework-specific routing quirks

Next.js API routes export specific method handlers — export async function POST. If you only export GET, the framework returns 405 automatically for POST. NestJS uses @Post() decorators; missing it = 405.

Fix It

// Express — explicitly POST + optional OPTIONS preflight
app.post('/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  webhookHandler
)

// Optional: handle CORS preflight
app.options('/webhooks/stripe', (req, res) => {
  res.set({
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST',
    'Access-Control-Allow-Headers': 'Content-Type, Stripe-Signature',
  })
  res.sendStatus(200)
})
// Next.js (App Router) — export POST handler
// app/api/webhooks/stripe/route.ts
import { NextRequest } from 'next/server'

export async function POST(req: NextRequest) {
  const rawBody = await req.text()
  // verify, process, return 200
  return new Response('ok')
}

// If GET also makes sense (e.g., health check):
export async function GET() {
  return new Response('ok')
}

How to Reproduce

From the command line: curl -X POST -d '{}' https://your-domain.com/webhook. If you get 405, your route is mis-registered. curl -X OPTIONS is the preflight test — if that 405s but POST works, you only need an OPTIONS handler.

Provider Notes

  • No major webhook provider sends GET, PUT, or DELETE for webhook deliveries. POST is universal.
  • Some providers send a one-time GET verification challenge when you register the URL — Meta's Graph API and Microsoft Graph webhooks do this. Handle GET separately for verification, POST for events.

Frequently Asked Questions

Do any providers use HTTP methods other than POST?

For event delivery, no. A few use GET for one-time URL verification when registering the webhook (Meta, Microsoft Graph), but the actual event deliveries are POST.

Should I handle OPTIONS preflight on webhook routes?

Only if your provider sends cross-origin requests, which is rare. Most webhook traffic is server-to-server and doesn't trigger preflight. Add OPTIONS only if you see actual preflight 405s in logs.

My route works locally but 405s in production. Why?

Almost always a reverse proxy difference. Check Caddy/nginx config in production for method filters. Also check the framework's production mode — some frameworks behave differently in serverless deployments.

Debug This Error in Real Time

WebhookWhisper captures every webhook request with full headers, body, and timing — so you can see exactly what the provider sent and reproduce the error instantly.

Start Debugging Free
Webhook 405 Method Not Allowed — Causes & Fixes (2026) | WebhookWhisper