{"record":{"id":"0f5253a7c3d17660","repo":"chenhg5/cc-connect","slug":"subscribe-w","errorCode":null,"errorMessage":"subscribe: %w","messagePattern":"subscribe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":237,"sourceCode":"\tmux.HandleFunc(p.webhookPath, p.webhookHandler)\n\tsrv := &http.Server{\n\t\tAddr:              p.webhookListen,\n\t\tHandler:           mux,\n\t\tReadHeaderTimeout: 10 * time.Second,\n\t}\n\t// Caller (Start) already holds p.mu, so assign directly — re-locking\n\t// a non-reentrant sync.RWMutex would deadlock.\n\tp.webServer = srv\n\n\tgo func() {\n\t\tslog.Info(\"max: webhook listening\", \"addr\", p.webhookListen, \"path\", p.webhookPath, \"url\", p.webhookURL)\n\t\tif err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\t\tslog.Error(\"max: webhook listener stopped\", \"err\", err)\n\t\t}\n\t}()\n\n\tif err := p.subscribe(ctx, p.webhookURL); err != nil {\n\t\treturn fmt.Errorf(\"subscribe: %w\", err)\n\t}\n\tslog.Info(\"max: webhook subscribed\", \"url\", p.webhookURL)\n\n\t// MAX has been observed to silently drop the webhook subscription\n\t// server-side without any delivery error. The documented 8h failure\n\t// window does not match the observed cadence (drops every 25–60min),\n\t// so we periodically re-POST the subscription. MAX overwrites the\n\t// existing registration in-place, so re-subscribing is idempotent.\n\tif p.resubscribeInterval > 0 {\n\t\tgo p.resubscribeLoop(ctx)\n\t}\n\treturn nil\n}\n\nfunc (p *Platform) resubscribeLoop(ctx context.Context) {\n\tt := time.NewTicker(p.resubscribeInterval)\n\tdefer t.Stop()\n\tfor {","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L219-L255","documentation":"startWebhook wraps the error from p.subscribe, which registers the webhook URL with the MAX API. This is the inner layer beneath the \"max: start webhook\" error and indicates the subscription HTTP call itself failed.","triggerScenarios":"Start (or the resubscribe loop) invoking subscribe with a webhook URL that the MAX API rejects: network failure, invalid URL, HTTP 4xx/5xx response, or invalid bot token.","commonSituations":"Bot token revoked/missing subscription scope, webhook URL not HTTPS or unreachable by MAX servers, transient network outage during a periodic resubscribe.","solutions":["Check the wrapped error: for \"HTTP 4xx/5xx\" inspect the API status and response body.","Verify the bot token is valid and has permission to manage subscriptions.","Confirm the webhook URL is publicly reachable over HTTPS from MAX servers.","Rely on the built-in resubscribe loop for transient drops; only intervene if failures are persistent."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// pre-check token validity\nresp, err := http.Get(\"https://botapi.max.ru/me?access_token=\" + token)\n// err != nil or non-200 → do not attempt webhook setup","typeGuard":null,"tryCatchPattern":"if err := p.Start(handler); err != nil {\n    if strings.Contains(err.Error(), \"subscribe:\") {\n        // schedule a delayed restart with backoff\n        time.AfterFunc(backoff, func() { retryStart() })\n    }\n}","preventionTips":["Use the built-in resubscribe loop rather than manual restarts on transient errors","Confirm webhook URL reachability and HTTPS before subscribing","Rotate/validate bot tokens before startup"],"tags":["go","webhook","http"],"backgroundTag":"api-request-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}