Connect a user's accounts by voice
A Pipbit device has no screen, so OAuth consent happens on a second screen — the same way Alexa and Google account linking work. Your agent starts the link in conversation, the platform brokers the hand-off to a browser at verify.pipbit.ai, and the token lands with your agent, stored per user. The platform never sees it.
The flow
- The user asks for something that needs their account. Ask for a spoken yes first.
- Your agent calls
agent.start_device_link(subject_id, provider_label="Google"). -
The platform picks the entry method: users with a verified phone are
texted a code; everyone else gets a
spoken three-word code back in the
DeviceLink. - Your agent tells the user where to go — verify.pipbit.ai, plus the code if it was spoken.
- The user enters the code in any browser, sees what is being connected, and confirms.
-
The page redirects to your provider's consent screen; after approval the
provider redirects to
GET /pipbit/oauth/callbackon your agent — a route the SDK serves automatically. The SDK verifies the signed state, exchanges the code, and stores the token under the user'ssubject_id. - Your
on_account_link(subject_id, token)hook fires — push a spoken confirmation. - Every later turn,
agent.get_oauth_token(subject_id)returns a live token, refreshed as needed.
Configuration
from pipbit import Agent, OAuth2Config, FileTokenStore
agent = Agent(
name="garry",
secret="pb_live_sk_...",
oauth=OAuth2Config(
authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
token_url="https://oauth2.googleapis.com/token",
client_id="...",
client_secret="...",
redirect_uri="https://your-host.example.com/pipbit/oauth/callback",
scope="https://www.googleapis.com/auth/gmail.readonly",
token_endpoint_auth="post", # Google wants id/secret in the body
extra_authorize_params={
"access_type": "offline", # without BOTH of these, Google issues
"prompt": "consent", # no refresh token and the link dies
},
),
token_store=FileTokenStore("tokens.json"), # MemoryTokenStore() for dev
on_account_link=my_linked_hook, # fn(subject_id, token)
)
redirect_uri must be HTTPS, point at
/pipbit/oauth/callback on your host, and be registered
identically with your provider. The authorize_url must be
HTTPS too — the platform rejects a plain-http one at
start_device_link (loopback is allowed for local mocks).
token_endpoint_auth is "basic" or
"post"; extra_authorize_params covers provider
quirks — the values above are exactly what Google needs for durable
refresh tokens.
The voice flow in your handler
def start_link(req):
link = agent.start_device_link(req.subject_id, provider_label="Google")
if link.method == "code" and link.user_code:
# no phone on file: speak the three-word code
return f"Go to verify.pipbit.ai and enter {link.user_code} to connect your Google account."
# verified phone: the platform texted the code — never speak it
return "I've texted you a code. Enter it at verify.pipbit.ai to connect your Google account."
Ask before you link (an explicit spoken yes, where "okay, no thanks" counts as no), and strip the URL scheme when speaking — "verify.pipbit.ai" reads better aloud than "https://…".
Tokens
agent.get_oauth_token(subject_id) returns a valid
OAuth2Token, refreshing behind a per-subject lock when
expired. If a refresh fails (revoked or expired grant) the stale token is
dropped and it returns None — your unlinked path runs and the
user re-links. FileTokenStore uses atomic writes, quarantines
corrupt files, and keeps the old refresh token when a provider omits a new
one. For multi-process deployments, implement the three-method
TokenStore ABC over Redis or a database.
Portal settings
In the developer portal, set Authentication mode to None
(public) even though your agent does OAuth: that dropdown gates
access before a conversation starts, while SDK account linking
happens during it, and start_device_link supplies
the per-user consent URL dynamically. Leave the auth configuration URL
blank.
Worked examples
Garry (Google — Gmail and Calendar) and the Sonos agent are complete
worked examples, including verbal confirmation state, spoken-code
handling, and on_account_link confirmations. Ask us for the
example source if you don't have repo access.