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

  1. Send health checks to a dedicated health endpoint, not /webhook/line
  2. When testing manually, use POST with a body (it will then fail signature validation with 403, proving the route works)
  3. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/bf7d43974fe80bdf. Report an issue: GitHub.