{"record":{"id":"bf7d43974fe80bdf","repo":"sipeed/picoclaw","slug":"method-not-allowed","errorCode":null,"errorMessage":"Method not allowed","messagePattern":"Method not allowed","errorType":"http","errorClass":null,"httpStatus":405,"severity":"info","filePath":"pkg/channels/line/line.go","lineNumber":143,"sourceCode":"}\n\n// WebhookPath returns the path for registering on the shared HTTP server.\nfunc (c *LINEChannel) WebhookPath() string {\n\tif c.config.WebhookPath != \"\" {\n\t\treturn c.config.WebhookPath\n\t}\n\treturn \"/webhook/line\"\n}\n\n// ServeHTTP implements http.Handler for the shared HTTP server.\nfunc (c *LINEChannel) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tc.webhookHandler(w, r)\n}\n\n// webhookHandler handles incoming LINE webhook requests.\nfunc (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Limit body size to prevent memory exhaustion (DoS).\n\t// ParseRequest reads r.Body internally via io.ReadAll; wrapping with\n\t// MaxBytesReader ensures oversized payloads are rejected before full\n\t// allocation.\n\tr.Body = http.MaxBytesReader(w, r.Body, maxWebhookBodySize)\n\n\tcb, err := webhook.ParseRequest(c.config.ChannelSecret.String(), r)\n\tif err != nil {\n\t\tvar maxBytesErr *http.MaxBytesError\n\t\tif errors.As(err, &maxBytesErr) {\n\t\t\tlogger.WarnC(\"line\", \"Webhook request body too large, rejected\")\n\t\t\thttp.Error(w, \"Request entity too large\", http.StatusRequestEntityTooLarge)\n\t\t} else if errors.Is(err, webhook.ErrInvalidSignature) {\n\t\t\tlogger.WarnC(\"line\", \"Invalid webhook signature\")\n\t\t\thttp.Error(w, \"Forbidden\", http.StatusForbidden)","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/line/line.go#L125-L161","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before: health probe misconfigured\nGET /webhook/line  → 405\n\n# after: probe a health route, or verify webhook liveness with POST\nPOST /webhook/line -d '{}'  → 401/403 invalid signature (expected)","handlingStrategy":"validation","validationCode":"// client side: only deliver LINE webhooks via POST with a signature\nreq, _ := http.NewRequest(http.MethodPost, webhookURL, bytes.NewReader(body))\nreq.Header.Set(\"Content-Type\", \"application/json\")\nreq.Header.Set(\"X-Line-Signature\", signature)","typeGuard":"func isLINEWebhookDelivery(r *http.Request) bool {\n    return r.Method == http.MethodPost && r.Header.Get(\"X-Line-Signature\") != \"\"\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["line","webhook","http","channels"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}