Budibase/budibase · error · HTTPError

Link token is invalid or has expired

Error message

Link token is invalid or has expired

What it means

handoffChatLinkSession looks up the stored chat identity link session by the token in the URL. If sdk.ai.chatIdentityLinks.getChatIdentityLinkSession returns nothing, the token does not exist, was already consumed, or has been cleaned up — so the flow cannot continue and a 400 is thrown.

Source

Thrown at packages/server/src/api/controllers/ai/chatIdentityLinks.ts:213

      if (window.opener && !window.opener.closed) {
        try {
          window.opener.focus()
          window.close()
        } catch (error) {}
      }
    </script>
  </body>
</html>`
}

export async function handoffChatLinkSession(
  ctx: UserCtx<void, string, { instance: string; token: string }>
) {
  const token = resolveToken(ctx.params.token)
  const session =
    await sdk.ai.chatIdentityLinks.getChatIdentityLinkSession(token)
  if (!session) {
    throw new HTTPError("Link token is invalid or has expired", 400)
  }
  assertSessionMatchesInstance({
    workspaceId: session.workspaceId,
    instance: ctx.params.instance,
  })

  if (!ctx.isAuthenticated) {
    utils.setCookie(
      ctx,
      `/api/chat-links/${ctx.params.instance}/${token}/handoff`,
      CHAT_LINK_RETURN_URL_COOKIE,
      { sign: false }
    )
    ctx.redirect(BUILDER_LOGIN_PATH)
    return
  }

  const currentGlobalUserId = getCurrentGlobalUserId(ctx)

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Request a fresh identity link from the chat channel and use the new token
  2. Verify the token string in the URL is complete and unmodified
  3. Check the backing store for the link session document (it may have been consumed or TTL-expired)
  4. If tokens expire too fast, adjust the session TTL/retention in chatIdentityLinks configuration

Example fix

// before
const session = await getChatIdentityLinkSession(oldToken) // already consumed
// after
const newToken = await createChatIdentityLink(...)
const session = await getChatIdentityLinkSession(newToken)
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await api.handoffLink(instance, token)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400 && e.message.includes("invalid or has expired")) {
    // issue a fresh link token in chat and retry once with the new token
  }
  throw e
}

Prevention

When it happens

Trigger: GET/POST to /api/chat-links/:instance/:token/handoff with a token that was never issued, already used (one-time handoff), or expired/deleted in the data store.

Common situations: Clicking a handoff link a second time after it was already completed; links left unused past their expiry/TTL; restarting with an in-memory or dev database that lost the session doc; typo'd or truncated token copied from chat.

Understand the failure class

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/9d6451fdd006d030. Report an issue: GitHub.