1 Overview
This NIP extends the EOSE message from NIP-01. A relay may append a third element: an array of hint strings. The hints tell the client whether the relay sent every stored event that matches the subscription's filters.
Plain EOSE marks the boundary between stored events and real-time events. It does not say whether more stored events exist. Relays commonly enforce an internal cap, for example 500 events per filter. This cap is independent of the client's limit. So clients today guess completeness with a heuristic. If fewer than limit events arrive, the client assumes the result is complete. Otherwise the client sends another REQ with until set to the oldest received created_at, and repeats.
This heuristic fails in two known ways. First, when the relay's internal cap is lower than the client's limit — cap 300, client asks for 500 — the client receives 300 events and wrongly concludes the result is complete. It silently misses data. Second, when the count of matching events equals the relay's cap exactly, the client cannot tell. It must send one more REQ just to observe zero results. A relay that caps at 300 is always queried at least twice for any subscription that exhausts the cap.
A single extra field on EOSE removes this ambiguity.
Read the subscription lifecycle in NIP-01 first. This chapter changes exactly one message in that flow: the EOSE that ends the stored-event phase.
2 The Hinted EOSE Message
A relay that implements this NIP MAY append a third element to EOSE. The element is an array of hint strings:
["EOSE", <subscription_id>, [<hint>, ...]]
The spec currently defines three hint values:
| Hint | Meaning | Client action |
|---|---|---|
| finish | The relay sent every stored event that matches the subscription's filters. | The client SHOULD NOT paginate this subscription further. |
| more | The relay holds more matching stored events than it sent. A relay MAY send this hint but is not required to. Detecting whether more events exist is not cheap on every storage backend. | The client SHOULD paginate to retrieve the rest. |
| auth | The relay may hold more matching stored events that unlock after NIP-42 AUTH. The relay MUST send an AUTH message with a challenge before the EOSE that carries this hint. | Authenticate, then query again. |
The array MAY carry multiple hints at the same time. The array MAY be empty. Clients MUST ignore unknown hint values without error. Future revisions of this NIP may define more hint values. Implementations SHOULD accept unknown hints and treat them as if absent.
The hints describe stored events only. They have no effect on real-time delivery. New events keep streaming under the regular NIP-01 rules until the client sends CLOSE or the relay sends CLOSED.
Presence is definitive; absence is not. When the relay omits the third element, or sends neither finish nor more, do not assume anything. Fall back to the legacy heuristic: paginate with until = oldest received created_at.
limit. The more hint keeps the client from stopping early; finish ends the loop.3 Ties on created_at
When a relay does not send finish, several events may share the oldest created_at in the response. A client that paginates with until = that timestamp risks missing events with the same timestamp. So relays SHOULD, when possible, advance the internal cursor past the boundary. Include all events with the boundary created_at in one response. Emit finish only when no older events remain.
4 Compatibility and Advertisement
The extension is backward compatible in both directions. Clients that do not implement this NIP ignore the extra element. Conforming JSON parsers accept arrays with trailing elements, and existing implementations index EOSE by position. Relays that do not implement this NIP keep sending two-element EOSE messages. Clients then fall back to the legacy heuristic from section 1.
Relays that implement this NIP SHOULD include 67 in the supported_nips field of their NIP-11 relay information document.
The third example in the source spec has mismatched subscription IDs: the REQ uses "sub2b" while the EVENT, AUTH response, and EOSE use "sub4". It also reuses "sub2b" from the second example. The transcripts below use one consistent ID per session.
5 Worked Examples
Three sessions against one relay. Orange lines go to the relay. Blue lines come from the relay.
-- 1. The relay sent every matching stored event -- → ["REQ", "sub2", {"ids": ["abcd…"]}] ← ["EVENT", "sub2", {"id":"abcd…", "kind":1, "content":"hello", …}] ← ["EOSE", "sub2", ["finish"]] -- the client does not paginate further -- -- 2. The relay holds more matching events -- → ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1]}] ← ["EVENT", "sub2b", {…}] … 300 events total (internal cap) … ← ["EOSE", "sub2b", ["more"]] → ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1], "until": 1751234567}] -- 3. More events exist behind AUTH; the public part is complete -- → ["REQ", "sub4", {"authors": ["abcd…"], "kinds": [1]}] ← ["EVENT", "sub4", {…}] ← ["AUTH", "challenge…"] ← ["EOSE", "sub4", ["auth", "finish"]] -- AUTH arrived before the EOSE, as required --
In session 3 the array carries two hints. finish says the unauthenticated result is complete. auth says authentication may unlock more. The client authenticates per NIP-42 and queries again.
6 Version History
NIP-67 is young and small. Notable changes, newest first:
- 20262026-09fiatjaf added the
authhint and the rule thatAUTHmust precede the hintedEOSE(#2371). - Origin2026-06mattn published the initial spec with the
finishandmorehints (#2317).
Original Proposal - Credits
| Author | mattn |
| First commit | 2026-06-06 — “NIP-67: EOSE Completeness Hint” (PR #2317) |
| Status | draft - optional — relays and clients work fine without it |
| Source | 67.md on GitHub - full change history |