1 Overview
Events on Nostr live forever by default. A relay stores a regular event and serves it to anyone who asks. Some content should not live forever. A flash sale ends. A meetup announcement goes stale. This NIP adds a self-destruct timer for such events.
The tool is one tag: expiration. The tag holds a unix timestamp in seconds. The timestamp marks the moment when the event SHOULD be considered expired. Relays and clients both apply this rule. Relays SHOULD delete the event at or after that moment.
Support is optional. A relay advertises support in its NIP-11 information document. A client checks that document before it publishes an expiring event.
Expiration is a housekeeping hint, not a security feature. The event is public on every relay until deletion. Any third party can download and keep a copy before it expires. Do not use this tag to protect conversations or secrets.
expiration moment. After it, relays stop serving the event and delete it when they can.2 The expiration Tag
Add one expiration tag to the tags array of any event. The tag has one value. The value is a unix timestamp in seconds, written as a string. The value is required.
tag: expiration values: - [UNIX timestamp in seconds]: required
| Tag | Value | Rule |
|---|---|---|
| expiration | unix timestamp, seconds, as a string | Required value. Marks the moment when the event SHOULD be considered expired and SHOULD be deleted by relays. |
The timestamp uses the same format as the created_at field from NIP-01: seconds since the unix epoch. Interpret it as the time at which relays should delete the event.
{
"pubkey": <pub-key>,
"created_at": 1000000000,
"kind": 1,
"tags": [
["expiration", "1600000000"]
],
"content": "This message will expire at the specified timestamp and be deleted by relays.\n",
"id": <event-id>
}
The tag value is a string, like every tag value in Nostr. Write "1600000000", with quotes. The created_at field next to it is a bare integer. Do not mix the two forms up.
3 Client Behavior
Not every relay implements this NIP. A relay that does lists the number 40 in the supported_nips field of its NIP-11 information document. Clients SHOULD use that field to learn if a relay supports this NIP.
- Clients SHOULD NOT send expiration events to relays that do not support this NIP.
- Clients SHOULD ignore events that have expired. Apply this rule even when a relay still delivers an expired event.
4 Relay Behavior
A relay that supports this NIP applies four rules:
- The relay is not required to delete an expired event at the exact expiration moment. It may delete late, and it may persist the event indefinitely.
- The relay SHOULD NOT send expired events to clients, even if it still stores them.
- The relay SHOULD drop any event that is already expired when a client publishes it.
- An expiration timestamp does not affect the storage of ephemeral events. Ephemeral kinds (see NIP-01) are not stored anyway.
The source spec writes "Relays MAY NOT delete expired messages immediately". "MAY NOT" is not an RFC 2119 keyword, and it can read as a prohibition. The next clause ("and MAY persist them indefinitely") shows the intent: deletion is permitted but not required to be immediate. This page restates that intent.
5 Suggested Use Cases
- Temporary announcements. An event organizer posts announcements about an upcoming event. The posts expire when the announcement is no longer useful.
- Limited-time offers. A business posts a special offer that is only available for a limited time. The offer expires after that time.
6 Worked Example
A cafe posts a lunch offer at 12:00 that expires at 15:00 the same day. The session below shows the publish, a fresh read, and a read after expiry. Orange lines go to the relay. Blue lines come from the relay.
-- 12:00 - the cafe publishes an offer that expires at 15:00 -- → ["EVENT", {"id":"7d1e02fa…","pubkey":"ac93be21…","created_at":1749812400, "kind":1,"tags":[["expiration","1749823200"]], "content":"Lunch special: soup + coffee, until 15:00 today!","sig":"91cc04d7…"}] ← ["OK", "7d1e02fa…", true, ""] -- 13:30 - a customer reads the feed; the offer is still fresh -- → ["REQ", "menu", {"kinds":[1], "authors":["ac93be21…"]}] ← ["EVENT", "menu", {"id":"7d1e02fa…", "content":"Lunch special: …", …}] ← ["EOSE", "menu"] → ["CLOSE", "menu"] -- 16:00 - after expiry, the relay no longer serves the event -- → ["REQ", "menu2", {"kinds":[1], "authors":["ac93be21…"]}] ← ["EOSE", "menu2"] -- publishing an already-expired event gets dropped -- → ["EVENT", {"id":"e4b09cd2…", "tags":[["expiration","1600000000"]], …}] ← ["OK", "e4b09cd2…", false, "invalid: event is expired"]
A careful client also ignores expired events on its own. If a lagging relay still delivers the offer at 16:00, the client compares the tag value with the current time and hides the event.
7 Version History
NIP-40 arrived in December 2022 and has changed little since. Notable changes, newest first:
- 20252025-12Added the
relaytag to the preamble, marking the NIP as one that relays implement. - 20232023-11fiatjaf reformatted the JSON example and removed the listed NIP authors, as part of a repo-wide cleanup.
- 20232023-04Grammar and typo fixes.
- 20222022-12A rapid series of edits filled in the spec: client behavior via
supported_nips, relay deletion rules, the ephemeral-event carve-out, use cases, and the privacy warning. - 20222022-12The tag value was fixed as a UNIX timestamp in seconds.
- Origin2022-12-04Thomas created
40.mdwith the expiration tag proposal.
Original Proposal - Credits
| Author | Thomas |
| First commit | 2022-12-04 — “Create 40.md” |
| Status | draft - optional - relay — relays advertise support via NIP-11 |
| Source | 40.md on GitHub - full change history |