> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # read_handle > Verify a signed handle and return the conversation id inside it. [ref-chatgateway]: /docs/server-sdks/reference/python/agents/chat-gateway [minthandle]: /docs/server-sdks/reference/python/agents/chat-gateway/mint-handle Return the conversation id inside a handle. The signature is verified first and the expiry second, both before the id is trusted for anything. ## **Parameters** **`handle`** `str` — required A handle this gateway issued, as returned by [`mint_handle()`][minthandle] or the `X-Chat-Handle` response header. --- ## **Returns** `str` — the conversation id. ## **Raises** `GatewayRejection`, carrying the status the browser should see: **`400 malformed handle`** `GatewayRejection` The handle is not two base64url parts, or its payload does not decode. --- **`403 invalid handle`** `GatewayRejection` The signature does not match, so this gateway did not issue it. --- **`403 expired handle`** `GatewayRejection` The handle is past its `handle_ttl`. --- An expired handle is normal rather than exceptional. Every long-lived conversation reaches it eventually, so treat it as "start a new conversation" instead of something to show the visitor. ## **Example** ```python {8} from signalwire.ai_chat import ChatGateway, GatewayRejection gateway = ChatGateway(config_url="https://bayview-taxi.example.com/swml") def conversation_for(handle: str) -> str | None: try: return gateway.read_handle(handle) except GatewayRejection as rejection: if rejection.status == 403: return None # expired or forged; start fresh raise ``` > Verify a signed handle and return the conversation id inside it.