sipeed/picoclaw · info
Method not allowed
Error message
Method not allowed
What it means
The shared LINE channel webhook handler (mounted at /webhook/line) accepts only POST; any other method gets an immediate 405 Method Not allowed response before signature validation. LINE Messaging API sends signed POSTs, so non-POST traffic here is by definition not from LINE.
Source
Thrown at pkg/channels/line/line.go:143
}
// WebhookPath returns the path for registering on the shared HTTP server.
func (c *LINEChannel) WebhookPath() string {
if c.config.WebhookPath != "" {
return c.config.WebhookPath
}
return "/webhook/line"
}
// ServeHTTP implements http.Handler for the shared HTTP server.
func (c *LINEChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.webhookHandler(w, r)
}
// webhookHandler handles incoming LINE webhook requests.
func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Limit body size to prevent memory exhaustion (DoS).
// ParseRequest reads r.Body internally via io.ReadAll; wrapping with
// MaxBytesReader ensures oversized payloads are rejected before full
// allocation.
r.Body = http.MaxBytesReader(w, r.Body, maxWebhookBodySize)
cb, err := webhook.ParseRequest(c.config.ChannelSecret.String(), r)
if err != nil {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
logger.WarnC("line", "Webhook request body too large, rejected")
http.Error(w, "Request entity too large", http.StatusRequestEntityTooLarge)
} else if errors.Is(err, webhook.ErrInvalidSignature) {
logger.WarnC("line", "Invalid webhook signature")
http.Error(w, "Forbidden", http.StatusForbidden)View on GitHub (pinned to 49183d7e8d)
Solutions
- Send health checks to a dedicated health endpoint, not /webhook/line
- When testing manually, use POST with a body (it will then fail signature validation with 403, proving the route works)
- Keep LINE console's webhook URL exactly as configured; no action needed if the 405 came from a stray probe
Example fix
# before: health probe misconfigured
GET /webhook/line → 405
# after: probe a health route, or verify webhook liveness with POST
POST /webhook/line -d '{}' → 401/403 invalid signature (expected) Defensive patterns
Strategy: validation
Validate before calling
// client side: only deliver LINE webhooks via POST with a signature
req, _ := http.NewRequest(http.MethodPost, webhookURL, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Line-Signature", signature) Type guard
func isLINEWebhookDelivery(r *http.Request) bool {
return r.Method == http.MethodPost && r.Header.Get("X-Line-Signature") != ""
} Prevention
- Aim uptime/health probes at a dedicated health endpoint, never at /webhook/line
- Expect 405 on GET as correct behavior — verify the route with a signed POST instead (it should then fail signature checks)
- Do not put browser-facing pages on the shared webhook listener
When it happens
Trigger: GET requests from a browser opening the webhook URL, uptime monitors / load-balancer health probes configured against the webhook path, curl without -X POST or -d, or an HTTP OPTIONS preflight attempt (the handler does not implement CORS).
Common situations: Pointing a health check at the same route as the webhook during setup; a human clicking the URL from logs/config to "test" it; verifying the endpoint is reachable with a plain GET.
Related errors
- Failed to load config
- Request failed with status ${res.status}
- Failed to fetch config
- Failed to save config
- create request: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/bf7d43974fe80bdf.
Report an issue: GitHub.