{"id":"3565a4e3817cc3fbaf3b3352f145427023c6d9052afab6d310131eefe8285f3a","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1780840132,"kind":30817,"tags":[["d","nut-17"],["title","NUT-17: WebSockets"],["summary","A JSON-RPC WebSocket protocol for subscribing to quote and proof state changes instead of polling for them."],["s","optional"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-17: WebSockets"],["client","openspecs-import"],["published_at","1719437548"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/17.md","web"],["x","5e8e5069db88aff5e2cb5f76b46fc99d2f543a0af0c21f740e83e071d9eea93a"]],"content":"# NUT-17: WebSockets\n\n`optional`\n\n`depends on: NUT-07`\n\n---\n\nThis NUT defines a websocket protocol that enables bidirectional communication between apps and mints using the JSON-RPC format.\n\n## Subscriptions\n\nThe websocket enables real-time subscriptions that wallets can use to receive notifications for a state change of a `MintQuoteResponse` ([NUT-04][04]), `MeltQuoteResponse` ([NUT-05][05]), `CheckStateResponse` ([NUT-07][07]).\n\nA summary of the subscription flow is the following:\n\n1. A wallet connects to the websocket endpoint and sends a `WsRequest` with the `subscribe` command.\n2. The mint responds with a `WsResponse` containing an ok or an error.\n3. If the subscription was accepted, the mint sends a `WsNotification` of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions.\n4. To close a subscription, the wallet sends `WsRequest` with the `unsubscribe` command.\n\n## Specifications\n\nThe websocket is reachable via the mint's URL path `/v1/ws`:\n\n```\nhttps://mint.com/v1/ws\n```\n\n`NUT-17` uses the JSON-RPC format for all messages. There are three types of messages defined in this NUT.\n\n### Requests\n\nAll requests from the wallet to the mint are of the form of a `WsRequest`:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"method\": <str_enum[WsRequestMethod]>,\n  \"params\": <str_WsRequestParams>,\n  \"id\": <int>\n}\n```\n\n`WsRequestMethod` is a enum of strings with the supported commands `\"subscribe\"` and `\"unsubscribe\"`:\n\n```ts\nenum WsRequestMethod {\n  sub = \"subscribe\",\n  unsub = \"unsubscribe\",\n}\n```\n\n`WsRequestParams` is a serialized JSON with the parameters of the corresponding command.\n\n#### Command: Subscribe\n\nTo subscribe to updates, the wallet sends a `\"subscribe\"` command with the following `params` parameters:\n\n```json\n{\n  \"kind\": <str_enum[SubscriptionKind]>,\n  \"subId\": <string>,\n  \"filters\": <string[]>\n}\n```\n\nHere, `subId` is a unique UUID v7 generated by the wallet and allows the client to map its requests to the mint's responses.\n\n`SubscriptionKind` is an enum with the following possible values:\n\n```ts\nenum SubscriptionKind {\n  bolt11_melt_quote = \"bolt11_melt_quote\",\n  bolt11_mint_quote = \"bolt11_mint_quote\",\n\n  bolt12_melt_quote = \"bolt12_melt_quote\",\n  bolt12_mint_quote = \"bolt12_mint_quote\",\n\n  proof_state = \"proof_state\",\n}\n```\n\nThe `filters` are an array of mint quote IDs ([NUT-04][04]), or melt quote IDs ([NUT-05][05]), or `Y`'s ([NUT-07][07]) of the corresponding object to receive updates from.\n\nAs an example, `filters` would be of the following form to subscribe for updates of three different mint quote IDs:\n\n```json\n[\"20385fc7245...\", \"d06667cda9b...\", \"e14d8ca96f...\"]\n```\n\nNote that `id` and `subId` are unrelated. The `subId` is the ID for each subscription, whereas `id` is part of the JSON-RPC spec and is an integer counter that must be incremented for every request sent over the websocket.\n\n**Important:** If the subscription is accepted by the mint, the mint MUST first respond with the _current_ state of the subscribed object and continue sending any further updates to it.\n\nFor example, if the wallet subscribes to a `Proof.Y` of a `Proof` that has not been spent yet, the mint will first respond with a `ProofState` with `state == \"UNSPENT\"`. If the wallet then spends this `Proof`, the mint would send a `ProofState` with `state == \"PENDING\"` and then one with `state == \"SPENT\"`. In total, the mint would send three notifications to the wallet.\n\n#### Command: Unsubscribe\n\nThe wallet should always unsubscribe any subscriptions that is isn't interested in anymore. The parameters for the `\"unsubscribe\"` command is only the subscription ID:\n\n```json\n{\n  \"subId\": <string>\n}\n```\n\n### Responses\n\nA `WsResponse` is returned by the mint to both the `\"subscribe\"` and `\"unsubscribe\"` commands and indicates whether the request was successful:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": <str>\n  },\n  \"id\": <int>\n}\n```\n\nHere, the `id` corresponds to the `id` in the request (as part of the JSON-RPC spec) and `subId` corresponds to the subscription ID.\n\n### Notifications\n\n`WsNotification`'s are sent from the mint to the wallet and contain subscription data in the following format\n\n```ts\n{\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": <str>,\n    \"payload\": NotificationPayload\n  }\n}\n```\n\n`subId` is the subscription ID (previously generated by the wallet) this notification corresponds to. `NotificationPayload` carries the subscription data which is a `MintQuoteResponse` ([NUT-04][04]), a `MeltQuoteResponse` ([NUT-05][05]), or a `CheckStateResponse` ([NUT-07][07]), depending on what the corresponding `SubscriptionKind` was.\n\n### Errors\n\n`WsErrors` for a given `WsRequest` are returned in the following format\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"error\": {\n    \"code\": -32601,\n    \"message\": \"Human readable error message\"\n  },\n  \"id\": \"1\"\n}\n```\n\n### Example: `ProofState` subscription\n\nTo subscribe to the `ProofState` of a `Proof`, the wallet establishes a websocket connection to `https://mint.com/v1/ws` and sends a `WsRequest` with a `filters` chosen to be the a `Proof.Y` value of the `Proof` (see [NUT-00][00]). Note that `filters` is an array meaning multiple subscriptions of the same `kind` can be made in the same request.\n\nWallet:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 0,\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"kind\": \"proof_state\",\n    \"filters\": [\n      \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\"\n    ],\n    \"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\"\n  }\n}\n```\n\nThe mint first responds with a `WsResponse` confirming that the subscription has been added.\n\nMint:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"result\": {\n    \"status\": \"OK\",\n    \"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\"\n  },\n  \"id\": 0\n}\n```\n\nThe mint immediately sends the current `ProofState` of the subscription as a `WsNotification`.\n\nMint:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"method\": \"subscribe\",\n  \"params\": {\n    \"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\",\n    \"payload\": {\n      \"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\",\n      \"state\": \"UNSPENT\",\n      \"witness\": null\n    }\n  }\n}\n```\n\nWhile leaving the websocket connection open, the wallet then spends the ecash. The mint sends `WsNotification` updating the wallet about state changes of the `ProofState` accordingly:\n\nMint:\n\n```json\n{\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"PENDING\"}}}\n\n{\"jsonrpc\": \"2.0\", \"method\": \"subscribe\", \"params\": {\"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\", \"payload\": {\"Y\": \"02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d\", \"state\": \"SPENT\"}}}\n```\n\nThe wallet then unsubscribes.\n\nWallet:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"unsubscribe\",\n  \"params\": { \"subId\": \"019e6d5a-2347-7000-8afa-051ae571f334\" }\n}\n```\n\n## Mint info setting\n\nMints signal websocket support via [NUT-06][06] using the following setting:\n\n```json\n\"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": <str>,\n          \"unit\": <str>,\n          \"commands\": <str[]>\n        },\n        ...\n      ]\n    }\n}\n```\n\nHere, `commands` is an array of the commands that the mint supports. A mint that supports all commands would return `[\"bolt11_mint_quote\", \"bolt11_melt_quote\", \"bolt12_mint_quote\", \"bolt12_melt_quote\", \"proof_state\"]`. Supported commands are given for each method-unit pair.\n\nExample:\n\n```json\n\"nuts\": {\n    \"17\": {\n      \"supported\": [\n        {\n          \"method\": \"bolt11\",\n          \"unit\": \"sat\",\n          \"commands\": [\n            \"bolt11_mint_quote\",\n            \"bolt11_melt_quote\",\n            \"proof_state\"\n            ]\n        },\n      ]\n    }\n}\n```\n\n[00]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crqqpd6d9\n[01]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crznyve7g\n[02]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cry0t0uzl\n[03]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crxuwwl3j\n[04]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crg74fknc\n[05]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cr2dsg4q4\n[06]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crv3ltsuz\n[07]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crwz62n00\n[08]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crs4q9zck\n[09]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crjx9yptm\n[10]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnqx5tye7\n[11]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnz43282n\n[12]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnyf7fzky\n","sig":"441b79e2f447b7eb517007bf2961e3d556f049246966869c30c691d016b7f0b44cfada8cd789d8e54ff94291a46510857c1ab296ed783c15b5d1dde60d64f925"}