{"record":{"id":"8e8a591d7a78de33","repo":"koala73/worldmonitor","slug":"webhook-registration-could-not-be-confirmed","errorCode":null,"errorMessage":"Webhook registration could not be confirmed","messagePattern":"Webhook registration could not be confirmed","errorType":"http","errorClass":"ApiError","httpStatus":503,"severity":"error","filePath":"server/worldmonitor/shipping/v2/register-webhook.ts","lineNumber":124,"sourceCode":"    callbackUrl,\n    chokepointIds: chokepointIds.length ? chokepointIds : [...VALID_CHOKEPOINT_IDS],\n    alertThreshold,\n    createdAt: new Date().toISOString(),\n    active: true,\n    secret,\n  };\n\n  const results = await runRedisPipeline([\n    ['SET', webhookKey(newSubscriberId), JSON.stringify(record), 'EX', String(WEBHOOK_TTL)],\n    ['SADD', ownerIndexKey(ownerTag), newSubscriberId],\n    ['EXPIRE', ownerIndexKey(ownerTag), String(WEBHOOK_TTL)],\n  ]);\n\n  if (!Array.isArray(results) || results.length !== 3 || results.some(result => !result || result.error)\n    || results[0]?.result !== 'OK'\n    || ![0, 1, '0', '1'].includes(results[1]?.result as number | string)\n    || (results[2]?.result !== 1 && results[2]?.result !== '1')) {\n    throw new ApiError(503, 'Webhook registration could not be confirmed', '');\n  }\n\n  return { subscriberId: newSubscriberId, secret };\n}\n","sourceCodeStart":106,"sourceCodeEnd":129,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/worldmonitor/shipping/v2/register-webhook.ts#L106-L129","documentation":"registerWebhook performs three Redis-backed registration steps (subscriber registration, owner-index add, and a final confirmation write) via Promise.all and inspects each result. If any step errors, returns an unexpected shape, or the confirmation codes are not the expected 'OK' / 0|'0' / 1|'1' values, it throws HTTP 503 'Webhook registration could not be confirmed' — the registration is treated as not committed.","triggerScenarios":"Any of the three Redis operations failing or returning malformed results during register-webhook: Redis unavailable, a script returning an unexpected value, TTL/TTL-type mismatch, or partial failure across the batch.","commonSituations":"Upstash/Redis transient errors or timeouts; cluster failover mid-write; Redis version/script incompatibility producing different result encodings; network blips between the API worker and Redis.","solutions":["Retry the registration with a new subscriberId after a short backoff — a partial registration expires via WEBHOOK_TTL","Verify webhook data-store health (Redis connectivity, latency, error rates) before repeated attempts","Confirm your subscriberId/secret handling tolerates re-registration (idempotent setup scripts)","If it reproduces consistently, check for Redis script/version drift; the expected confirmation codes are 'OK', 0|'0', and 1|'1'"],"exampleFix":"// before\nconst { subscriberId, secret } = await client.registerWebhook({ callbackUrl });\n// after\nlet reg;\ntry {\n  reg = await client.registerWebhook({ callbackUrl });\n} catch (e) {\n  if (e.status === 503) reg = await retryWithBackoff(() => client.registerWebhook({ callbackUrl }));\n  else throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  reg = await client.registerWebhook({ callbackUrl });\n} catch (e) {\n  if (e.status === 503 && /could not be confirmed/.test(e.message)) {\n    reg = await retryWithBackoff(() => client.registerWebhook({ callbackUrl }), { retries: 3 });\n  }\n  throw e;\n}","preventionTips":["Make webhook setup idempotent; a failed attempt self-expires via TTL","Retry on 503 with backoff before surfacing failure","Verify the webhook with a list/test delivery after registration"],"tags":["redis","webhook","http-503","consistency"],"backgroundTag":"unexpected-response-shape","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}