POST /events NIP-98 requirements are undocumented, and the in-repo e2e tests use a dev-only auth path that fails against production
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
`POST /events` on the relay requires NIP-98 auth, but the requirements aren't documented anywhere I could find, and the in-repo e2e tests use an auth path that only works in dev — so copying the tests against a production relay fails with a 401 and no indication why.
Working this out took a while, and none of it is discoverable from the docs. Writing it down here in case it's useful, and I'm happy to send a docs PR if you'd like it (opening the issue first, per CONTRIBUTING).
## The misleading part
The e2e tests authenticate with an `X-Pubkey` header. That header is a dev-mode fallback gated on `require_auth_token` (`crates/buzz-relay/src/api/bridge.rs:117`). Against a production relay it returns:
```
401 {"error":"missing Nostr auth"}
```
So the natural move — copy what the tests do — produces an error that says nothing about the header being dev-only, and there's no doc to contradict the tests.
## What actually works
`Authorization: Nostr `, where the event must satisfy (per `crates/buzz-auth/src/nip98.rs`):
- `kind` = 27235
- `created_at` within ±60s of the relay's clock
- a **single-letter `u` tag** whose value matches the normalized request URL — trailing slash stripped. **Not** a multi-letter `url` tag.
- a `method` tag matching the HTTP method
- a `payload` tag = SHA-256 hex of the request body
The `u`-vs-`url` tag detail is the one that cost us the most time. NIP-98 in the wild is inconsistent about it, the failure is an opaque 401, and there's nothing to check your work against.
## Suggested fix
A short section in the docs covering: which endpoints require NIP-98, the five requirements above, one worked example of a valid auth event, and an explicit note that `X-Pubkey` is dev-only and will not work against a normal deployment. Ideally also a comment in the e2e tests saying the same, since those are what people copy.
Happy to write it — say the word and I'll open a PR with DCO sign-off.
## Possibly related
- #6477 — docs gap on `POST /query` (bare filter objects vs. the required array wrapper). Same class of problem on a neighbouring endpoint; might be worth fixing together.
- #6441 — NIP-98 verification validating against a normalization of `RELAY_URL` that drops the port, breaking self-hosted relays on non-443 ports. Same code path as the `u` tag matching above.
- #6123 — asks for service-to-service auth as an alternative to NIP-98, and describes the same burden from the other direction.
## Environment
Self-hosted `buzz-relay` (`ghcr.io/block/buzz:main`, image built 2026-08-09), reached over HTTPS on 443 behind a reverse proxy.
Contributor guide
Research direction
Start with crates/buzz-auth/src/nip98.rs and crates/buzz-relay/src/api/bridge.rs:117 to confirm the authentication requirements and the dev-only X-Pubkey path. Find the existing API documentation and in-repo e2e tests, then add the requested NIP-98 section, worked example, and test comment. Done means production users can identify the required endpoints and construct a valid request without relying on X-Pubkey.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, authentication, documentation
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100