draft optional
This NIP defines how to share and run webxdc apps over Nostr. Webxdc apps are .xdc (ZIP) files containing sandboxed HTML5 applications. They are attached to regular Nostr events using imeta tags (NIP-92), and state is coordinated through a unique identifier.
This spec covers public webxdc communication only. Private communication may be addressed in a future update.
A webxdc app is attached to any event by including the .xdc file URL in the content and an imeta tag with MIME type application/x-webxdc.
The imeta tag SHOULD include a webxdc property with a randomly generated unique string. This serves as the coordination identifier for state updates and realtime channels. If omitted, the app can still run but state won't work.
{
"kind": 1,
"content": "Let's play chess! https://blossom.example.com/abc123.xdc",
"tags": [
["imeta",
"url https://blossom.example.com/abc123.xdc",
"m application/x-webxdc",
"x a1b2c3d4e5f6...",
"webxdc 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
]
]
}A webxdc MAY also be published as a kind 1063 (NIP-94) file metadata event:
{
"kind": 1063,
"content": "A collaborative chess game. Play with friends over Nostr!",
"tags": [
["url", "https://blossom.example.com/abc123.xdc"],
["m", "application/x-webxdc"],
["x", "a1b2c3d4e5f6..."],
["alt", "Webxdc app: Chess"],
["webxdc", "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"]
]
}A regular event carrying a state update, mapping to the webxdc sendUpdate() API. Updates are ordered by created_at and assigned serial numbers by the client.
i: Thewebxdcidentifier from the originating event (required)alt: NIP-31 human-readable description (required)info: Short info message, max ~50 chars (optional)document: Document name being edited (optional)summary: Short summary text, e.g. "8 votes" (optional)
The optional tags correspond to fields in the webxdc sendUpdate() API.
JSON-serialized payload from sendUpdate().
{
"kind": 4932,
"content": "{\"move\":\"e2e4\",\"player\":\"white\"}",
"tags": [
["i", "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"],
["alt", "Webxdc update"],
["info", "White played e2-e4"]
]
}An ephemeral event carrying realtime data, mapping to the webxdc joinRealtimeChannel API. Relays forward these to active subscribers but do not store them.
i: Thewebxdcidentifier from the originating event (required)
Base64-encoded Uint8Array payload (max 128,000 bytes raw).
{
"kind": 20932,
"content": "SGVsbG8gZnJvbSBucHViMWFiYy4uLg==",
"tags": [
["i", "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"]
]
}- A user uploads a
.xdcfile (e.g. to Blossom) and publishes an event with the URL in content and animetatag. TheimetaSHOULD include awebxdcproperty. - A client detects the
imetatag, downloads the.xdc, extracts it, and runsindex.htmlin a sandboxed iframe or webview. sendUpdate()publishes a kind4932event with thewebxdcidentifier in anitag.- The client subscribes to kind
4932events with#imatching the identifier and delivers them viasetUpdateListener(). joinRealtimeChannel()subscribes to kind20932events with#imatching the identifier.send()publishes ephemeral kind20932events.leave()closes the subscription.selfAddrandselfNameMAY map to the user's npub and display name, or any other values.
- Webxdc apps MUST be sandboxed with no network access, per the webxdc spec.
- Clients SHOULD verify the
.xdcfile hash (xtag) before running it. - All communication in this spec is public. Webxdc apps designed for private chats or small groups may not work as expected.
- Webxdc apps have no access to Nostr signatures or identity verification. Any participant can claim to be anyone within the app. Apps should not rely on
selfAddrorselfNamefor trust decisions.