12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-39 - Linking Profiles to Other Platforms — Nostr Instruction Manual</title>7<meta name="description" content="NIP-39: link a Nostr profile to GitHub, Twitter, Mastodon, and Telegram accounts with kind 10011 identity claims and verifiable proofs.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 39 - Identity</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="39">39</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 39</div>50 <h1>Linking Profiles to Other Platforms</h1>51 <p class="sub">One event lists your accounts on other platforms, each with a public proof that anyone can check.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>A Nostr public key is just hex. It does not tell other users who you are. This NIP lets a user declare: this GitHub account, this Twitter account, this Mastodon account, and this Telegram account are all mine.</p>60 <p>The declaration alone proves nothing. Anyone can type someone else's username. So each claim carries a <strong>proof</strong>: a post the user made <em>on the other platform</em> that names their Nostr public key. A client fetches the proof and compares the keys. If they match, the link is real in both directions.</p>61 <p>Claims live in a dedicated event of kind <code>10011</code>, as <code>i</code> tags. The kind is replaceable under the <a href="01.html">NIP-01</a> storage rules, so a relay keeps only the latest identity list per pubkey.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>NIP-39 links you to <em>other platforms</em>. To link a Nostr key to a domain name you control, use <a href="05.html">NIP-05</a> instead. The two work well together.</p>66 </div>6768 <h2 class="bar" id="spec"><span class="num">2</span> The Identity Event and the i Tag</h2>69 <p>Publish one event of kind <code>10011</code>. Put one <code>i</code> tag in its <code>tags</code> array for each claimed identity.</p>7071 <div class="crt">72 <div class="titlebar">Wire format - kind 10011 identity claims</div>73<pre>{74 <span class="k">"kind"</span>: 10011,75 <span class="k">"tags"</span>: [76 [<span class="s">"i"</span>, <span class="s">"github:semisol"</span>, <span class="s">"9721ce4ee4fceb91c9711ca2a6c9a5ab"</span>],77 [<span class="s">"i"</span>, <span class="s">"twitter:semisol_public"</span>, <span class="s">"1619358434134196225"</span>],78 [<span class="s">"i"</span>, <span class="s">"mastodon:bitcoinhackers.org/@semisol"</span>, <span class="s">"109775066355589974"</span>],79 [<span class="s">"i"</span>, <span class="s">"telegram:1087295469"</span>, <span class="s">"nostrdirectory/770"</span>]80 ],81 <span class="c">// other fields...</span>82}</pre>83 </div>8485 <p>An <code>i</code> tag must have two parameters:</p>86 <div class="tscroll"><table class="ref">87 <caption>i tag parameters</caption>88 <tr><th>#</th><th>Parameter</th><th>Rule</th></tr>89 <tr><td class="btn">1</td><td><code>platform:identity</code></td><td>The platform name (for example <code>github</code>) and the identity on that platform (for example <code>semisol</code>), joined with <code>:</code>.</td></tr>90 <tr><td class="btn">2</td><td><code>proof</code></td><td>A string or object that points to the proof of owning this identity.</td></tr>91 </table></div>9293 <p>Three normative rules apply to the tag:</p>94 <ul>95 <li>Clients should process <code>i</code> tags with more than 2 values. Extra values allow future extension.</li>96 <li>A platform name should use only <code>a-z</code>, <code>0-9</code>, and the characters <code>._-/</code>. It must not include <code>:</code>.</li>97 <li>Normalize identity names when possible. Replace uppercase letters with lowercase letters. If an entity has several aliases, use the primary one.</li>98 </ul>99100 <figure class="diagram">101 <div class="frame">102 <svg viewBox="0 0 560 130" role="img" aria-label="Anatomy of an i tag: tag name, platform and identity joined by a colon, and the proof pointer">103 <rect class="d-actor" x="10" y="40" width="60" height="46" rx="8"/>104 <text class="d-actor-label" x="40" y="68" text-anchor="middle">"i"</text>105 <rect class="d-actor" x="100" y="40" width="230" height="46" rx="8"/>106 <text class="d-actor-label" x="215" y="68" text-anchor="middle">"github:semisol"</text>107 <rect class="d-actor" x="360" y="40" width="190" height="46" rx="8"/>108 <text class="d-actor-label" x="455" y="68" text-anchor="middle">"9721ce4e…a5ab"</text>109 <text class="d-note" x="40" y="105" text-anchor="middle">tag name</text>110 <text class="d-note" x="215" y="105" text-anchor="middle">platform : identity</text>111 <text class="d-note" x="455" y="105" text-anchor="middle">proof (here: a Gist ID)</text>112 <text class="d-label" x="215" y="28" text-anchor="middle">no ":" allowed in the platform name</text>113 </svg>114 </div>115 <figcaption>Fig. 39-1 — <b>i tag anatomy.</b> Two parameters: who you are over there, and where the proof lives.</figcaption>116 </figure>117118 <div class="box tech">119 <span class="tag">Tech Note</span>120 <p>The example object in the source spec is not valid JSON: a comma is missing between the <code>mastodon</code> and <code>telegram</code> tag lines. The example above adds it. Also, the abstract mentions claims over "keypairs", but the spec defines no keypair claim type. A <code>pgp</code> type existed for three days in 2024-02 and was removed "temporarily"; it never returned.</p>121 </div>122123 <h2 class="bar" id="claims"><span class="num">3</span> Claim Types</h2>124 <p>The spec defines four platforms. For each one it fixes three things: what the identity string is, what the proof points to, and the exact text the proof post must contain. <code><npub></code> below is the user's public key in the bech32 <code>npub</code> encoding from <a href="19.html">NIP-19</a>.</p>125126 <div class="tscroll"><table class="ref">127 <caption>Claim types</caption>128 <tr><th>Platform</th><th>Identity</th><th>Proof</th><th>Proof location</th></tr>129 <tr><td class="btn">github</td><td>a GitHub username</td><td>a Gist ID</td><td><code>https://gist.github.com/<identity>/<proof></code></td></tr>130 <tr><td class="btn">twitter</td><td>a Twitter username</td><td>a Tweet ID</td><td><code>https://twitter.com/<identity>/status/<proof></code></td></tr>131 <tr><td class="btn">mastodon</td><td><code><instance>/@<username></code></td><td>a post ID</td><td><code>https://<identity>/<proof></code></td></tr>132 <tr><td class="btn">telegram</td><td>a Telegram user ID</td><td><code><ref>/<id></code></td><td><code>https://t.me/<proof></code></td></tr>133 </table></div>134135 <h3>github</h3>136 <p>The proof is a Gist that <code><identity></code> created. The Gist has a single file with the text: <code>Verifying that I control the following Nostr public key: <npub encoded public key></code>.</p>137138 <h3>twitter</h3>139 <p>The proof is a Tweet that <code><identity></code> posted. The Tweet has the text: <code>Verifying my account on nostr My Public Key: "<npub encoded public key>"</code>.</p>140141 <h3>mastodon</h3>142 <p>The proof is a post that <code><username>@<instance></code> published. The post has the text: <code>Verifying that I control the following Nostr public key: "<npub encoded public key>"</code>.</p>143144 <h3>telegram</h3>145 <p>The proof is a message in the public channel or group named <code><ref></code>, with message ID <code><id></code>. User ID <code><identity></code> sent the message. The message has the text: <code>Verifying that I control the following Nostr public key: "<npub encoded public key>"</code>.</p>146147 <div class="box tech">148 <span class="tag">Tech Note</span>149 <p>The required proof text is not uniform. The <code>github</code> text has no quotes around the npub; the other three quote it. The <code>twitter</code> text uses a different sentence and no final period, exactly as written in the spec. A verifier must match the exact per-platform string, not one shared template.</p>150 </div>151152 <div class="box warn">153 <span class="tag">Warning!</span>154 <p>Always verify. An <code>i</code> tag with a missing, deleted, or mismatched proof is only a claim. Show unverified claims differently, or not at all. Never treat the tag alone as evidence.</p>155 </div>156157 <figure class="diagram">158 <div class="frame">159 <svg viewBox="0 0 560 300" role="img" aria-label="Sequence diagram: client fetches the kind 10011 event from a relay, then fetches the proof from the external platform and compares public keys">160 <line class="d-wire" x1="90" y1="40" x2="90" y2="285" stroke-dasharray="3 4"/>161 <line class="d-wire" x1="300" y1="40" x2="300" y2="285" stroke-dasharray="3 4"/>162 <line class="d-wire" x1="490" y1="40" x2="490" y2="285" stroke-dasharray="3 4"/>163 <rect class="d-actor" x="35" y="8" width="110" height="30" rx="6"/>164 <text class="d-actor-label" x="90" y="28" text-anchor="middle">Client</text>165 <rect class="d-actor" x="245" y="8" width="110" height="30" rx="6"/>166 <text class="d-actor-label" x="300" y="28" text-anchor="middle">Relay</text>167 <rect class="d-actor" x="435" y="8" width="110" height="30" rx="6"/>168 <text class="d-actor-label" x="490" y="28" text-anchor="middle">Platform</text>169 <path class="d-msg" d="M90,62 L295,62"/>170 <text class="d-label" x="195" y="55" text-anchor="middle">REQ {"kinds":[10011],…}</text>171 <path class="d-msg blue" d="M300,95 L95,95"/>172 <text class="d-label" x="195" y="88" text-anchor="middle">EVENT with i tags</text>173 <path class="d-msg d-flow" d="M90,140 L485,140"/>174 <text class="d-label" x="288" y="133" text-anchor="middle">GET gist.github.com/semisol/9721ce4e…</text>175 <path class="d-msg blue d-flow" d="M490,175 L95,175"/>176 <text class="d-label" x="292" y="168" text-anchor="middle">"Verifying that I control … npub1…"</text>177 <path class="d-msg" d="M90,220 L200,220 L200,240 L95,240"/>178 <text class="d-label" x="290" y="233" text-anchor="middle">npub in proof == event pubkey? → verified</text>179 <text class="d-note" x="290" y="270" text-anchor="middle">Repeat for every i tag. A failed fetch or mismatch = unverified claim.</text>180 <circle r="4" fill="#f26a00">181 <animateMotion dur="2.6s" repeatCount="indefinite" path="M90,140 L485,140"/>182 </circle>183 </svg>184 </div>185 <figcaption>Fig. 39-2 — <b>Verification flow.</b> The relay supplies the claim. The other platform supplies the proof. The client compares.</figcaption>186 </figure>187188 <h2 class="bar" id="examples"><span class="num">4</span> Worked Example</h2>189 <p>Alice controls the GitHub account <code>alice-dev</code> and the Mastodon account <code>@alice@mastodon.social</code>. She posts a proof on each platform, then publishes one kind <code>10011</code> event:</p>190191 <div class="crt">192 <div class="titlebar">Alice's identity event</div>193<pre>{194 <span class="k">"id"</span>: <span class="s">"7f3c1a9d20…e4b8"</span>,195 <span class="k">"pubkey"</span>: <span class="s">"a1b2c3d4e5…9f00"</span>,196 <span class="k">"created_at"</span>: 1767225600,197 <span class="k">"kind"</span>: 10011,198 <span class="k">"tags"</span>: [199 [<span class="s">"i"</span>, <span class="s">"github:alice-dev"</span>, <span class="s">"c3d9e2f8a41b7c60d5a2e91f0b8c4d73"</span>],200 [<span class="s">"i"</span>, <span class="s">"mastodon:mastodon.social/@alice"</span>, <span class="s">"113954218847001122"</span>]201 ],202 <span class="k">"content"</span>: <span class="s">""</span>,203 <span class="k">"sig"</span>: <span class="s">"88d1a0f2c7…31ab"</span>204}</pre>205 </div>206207 <p>Bob's client receives this event and verifies each claim:</p>208 <div class="crt">209 <div class="titlebar">Verification session - Bob's client</div>210<pre><span class="c">-- fetch the claims from a relay --</span>211<span class="send">→ ["REQ", "ids", {"kinds":[10011], "authors":["a1b2c3d4e5…9f00"]}]</span>212<span class="recv">← ["EVENT", "ids", {"kind":10011, "tags":[["i","github:alice-dev","c3d9e2f8…"], …]}]</span>213<span class="recv">← ["EOSE", "ids"]</span>214215<span class="c">-- claim 1: github. Build the proof URL and fetch it --</span>216<span class="c">GET https://gist.github.com/alice-dev/c3d9e2f8a41b7c60d5a2e91f0b8c4d73</span>217<span class="c">file body: "Verifying that I control the following Nostr public key: npub15dj…8ftq"</span>218<span class="c">npub15dj…8ftq decodes to a1b2c3d4e5…9f00 → MATCH. Verified.</span>219220<span class="c">-- claim 2: mastodon --</span>221<span class="c">GET https://mastodon.social/@alice/113954218847001122</span>222<span class="c">post text: "Verifying that I control the following Nostr public key: \"npub15dj…8ftq\""</span>223<span class="c">→ MATCH. Verified. Bob's client shows both badges on Alice's profile.</span></pre>224 </div>225226 <h2 class="bar" id="history"><span class="num">5</span> Version History</h2>227 <p>NIP-39 started as extra tags inside the kind-0 metadata event. In 2026 the claims moved into their own replaceable kind. Notable changes, newest first:</p>228 <ul class="patchnotes">229 <li><span class="ver">2026</span><span class="when">2026-06</span>Renamed to "Linking Profiles to Other Platforms" for clarity.</li>230 <li><span class="ver">2026</span><span class="when">2026-02</span>Moved <code>i</code> tags out of the kind-0 metadata event into the new replaceable kind <code>10011</code>.</li>231 <li class="minor"><span class="ver">2024</span><span class="when">2024</span>Clarified that <code>i</code> tags belong in the <code>tags</code> array; JSON and link formatting fixes.</li>232 <li><span class="ver">2024</span><span class="when">2024-02</span>Added a <code>pgp</code> claim type, then removed it "temporarily" three days later.</li>233 <li class="minor"><span class="ver">2023</span><span class="when">2023-11</span>Author lists removed from all NIPs; JSON reformatting.</li>234 <li><span class="ver">2023</span><span class="when">2023-03</span>Readability pass by Semisol days after the merge.</li>235 <li><span class="ver">Origin</span><span class="when">2023-03-09</span>pseudozach merged the first version: "NIP-39 external identities in metadata".</li>236 </ul>237238 <div class="credits">239 <h3 class="cardhead">Original Proposal - Credits</h3>240 <table>241 <tr><td>Author</td><td>pseudozach</td></tr>242 <tr><td>First commit</td><td>2023-03-09 — “NIP-39 external identities in metadata”</td></tr>243 <tr><td>Status</td><td>draft - optional — clients may ignore identity claims entirely</td></tr>244 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/39.md">39.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/39.md">full change history</a></td></tr>245 </table>246 </div>247248 <h2 class="bar"><span class="num">6</span> See Also</h2>249 <ul>250 <li><a href="01.html">NIP-01</a> — the event format, the <code>tags</code> array, and the replaceable storage class of kind <code>10011</code>.</li>251 <li><a href="19.html">NIP-19</a> — the <code>npub</code> encoding that every proof text must contain.</li>252 <li><a href="05.html">NIP-05</a> — maps a Nostr key to a DNS-based identifier; the domain-name counterpart to this NIP.</li>253 <li><a href="24.html">NIP-24</a> — extra fields for the kind-0 metadata event, where these claims used to live.</li>254 </ul>255 </div>256257 <aside>258 <div class="cartridge">259 <div class="inner">260 <h3 class="cardhead">Spec Card</h3>261 <div class="lights"><span class="led on"></span><span class="led warn"></span><span class="led warn"></span></div>262 <dl>263 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip optional">optional</span></dd>264 <dt>Defines</dt><dd>kind 10011 - i tag - 4 claim types</dd>265 <dt>Kind(s)</dt><dd>10011 (replaceable)</dd>266 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="19.html">NIP-19</a> (npub)</dd>267 <dt>Transport</dt><dd>proofs fetched over HTTPS</dd>268 <dt>First commit</dt><dd>2023-03-09</dd>269 <dt>Last updated</dt><dd>2026-06-13</dd>270 </dl>271 </div>272 </div>273 </aside>274 </main>275276 <nav class="navbtns">277 <a href="38.html">◀ NIP-38: User Statuses</a>278 <a href="40.html">NIP-40: Expiration Timestamp ▶</a>279 </nav>280281 <footer class="pagefoot">282 <a href="../index.html">Nostr Protocol Manual</a>283 <span class="pageno">NIP 39 / F4</span>284 </footer>285</div>286287<script src="../assets/webmcp.js" defer></script>288</body>289</html>