Skip to main content

Identity Connection (Kind 35521)

A Kind 35521 is a publicly verifiable Nostr event published by an end-user to announce that their Nostr public key is linked to a Legacy Identity Provider (LIDP) account.

Crucially, because an end-user could lie about owning an account, Kind 35521 alone is insufficient. It must reference cryptographic proof signed by a trusted third party—an Identity Authority (IA).

Event Structure

{
"kind": 35521,
"pubkey": "<user_pubkey>",
"created_at": "<unix_timestamp>",
"tags": [
["d", "<connection_key>"],
["e", "<attestation_event_id>", "<ia_relay_url>"],
["lidp", "<lidp_name>"]
],
"content": "<public_profile_json>",
"id": "...",
"sig": "..."
}

Required Tags

TagFormatDescription
d["d", "<connection_key>"]Deterministic connection key: SHA256(lidp_name:lidp_id) (e.g., a1b2c3...). Note: Including a platform prefix here is strictly invalid.
lidp["lidp", "<lidp_name>"]The name of the Legacy Identity Provider (e.g., discord, x) for quick relay filtering.
e["e", "<attestation_event_id>", "<ia_relay_url>"]A reference to the Kind 35522 attestation event signed by the IA. Clients must fetch the full event from the IA relay to verify. See below.

The nconnection String Format

To make identity links portable and easy for users to share between clients (e.g., from a Discord bot to a mobile wallet), Zapf uses a custom bech32-encoded string format with the prefix nconnection.

An nconnection string bundles the deterministic connection_key with the relays where the user's Kind 35521 events are published.

Encoding Structure (TLV)

The format follows a Type-Length-Value (TLV) structure similar to NIP-19 identifiers:

TypeNameLengthDescription
0Special32 bytesThe raw bytes of the connection_key (hex decoded).
1RelayVariableA UTF-8 string of a Nostr relay URL. Can be repeated.

Example: nconnection1qqpx9er9wehxum59ahx7u3z9yhxw7msv9ujumn9wskz6un9d3shjtnyv9khq6t9wshx67m9vsuqgyp96mzk6uayz9p8m94qqqqqqqf4k9mx

The e Tag and Multi-IA Stacking

The e tag references the Kind 35522 attestation published by the IA to its relay.

  • Index 1: The event ID of the IA's Kind 35522 attestation.
  • Index 2: A relay hint where clients must fetch the full Kind 35522 to verify.

The attestation is not embedded in the Kind 35521 event. Clients fetch it from the IA relay on demand. This keeps the event lightweight (~100 bytes per attestation reference vs. ~1–2 KB for embedded JSON).

Stacking: A user can include multiple e tags in their Kind 35521 event. If a user proves their Discord account to three different IAs, they can reference all three attestations. A client only needs to trust one of those IAs to consider the connection verified.

Content Field

The content field contains the user's profile metadata as cleartext JSON. It includes display information passed down from the IA, as well as key identity fields so clients can display the connection without fetching the attestation from the relay:

{
"display_name": "loki_nakamo",
"picture": "https://cdn.discord.com/...",
"user_id": "123456789",
"username": "loki_nakamo"
}
FieldSourceDescription
display_nameIA profile dataHuman-readable display name on the LIDP
pictureIA profile dataAvatar URL
user_idIA evidencePlatform-specific numeric or string user ID
usernameIA evidencePlatform handle / username

All content fields are globally readable on the Nostr network. The authoritative values for user_id and username are in the Kind 35522 evidence tag — the content fields here are a convenience copy for quick display.

Security & Spoofing

Because Kind 35521 is a user-signed event, a user could theoretically place malicious or spoofed data into the content field (e.g., claiming their display name is "Elon Musk" when it is not).

This does not affect the protocol:

  1. Routing ignores content: The ZSP strictly routes payments based on the cryptographic hash in the d tag. A user cannot spoof the payment destination by changing their event content.
  2. Verification catches spoofing: The "e" tag references the unforgeable Kind 35522 attestation signed by the IA. Clients must cross-reference the content claims against the IA's verified payload fetched from the relay. If they do not match, the client UI should flag the connection as spoofed.