Publish a public attendance attestation event on ticket register #20

Open
opened 2026-05-24 15:18:13 +00:00 by padreug · 0 comments
Owner

Context

PR #19 added events_ticket_register over the LNbits nostr
transport. The transport encrypts every RPC event (NIP-44 v2) to
the server's transport pubkey, so the registration record is
visible only to the organizer + the server. For ticket-holder-side
verifiability — and as a stepping stone toward fully-Nostr-native
scanning where the webapp publishes the attestation directly
without a backend round-trip — the events extension should ALSO
publish an unencrypted attestation event when a ticket flips to
registered.

Proposed shape

Modeled on the existing publish_or_delete_nostr_event pattern in
nostr_hooks.py, which signs and broadcasts on the organizer's
keypair. New publish call site lives in services.py (or a
dedicated services.register_attestation), invoked from BOTH
register paths so the wire signal is consistent:

  1. Inside transport_rpcs.handle_events_ticket_register — right
    after the update_ticket(ticket) line.
  2. Inside the legacy HTTP api_event_register_ticket
    (views_api.py) — same hook for the LNbits admin Quasar UI.

Event proposal

  • Kind: pick from a custom range (e.g. 30030 — addressable
    replaceable so the registrar can re-publish if they need to
    correct a bad scan; OR a regular kind in the 1000-9999 range if
    we want it ephemeral). Probably 30030 for replaceability.
  • Tags:
    • ["d", "<ticket_id>"] — addressable identifier so the
      organizer can only have one "registered" attestation per ticket
      (re-publishes replace).
    • ["a", "<31922|31923>:<organizer_pubkey>:<event_d_tag>"] — NIP-19
      reference to the parent calendar event.
    • ["p", "<ticket_holder_pubkey>"] — if the ticket has a
      nostr_identifier and it resolves to a pubkey, surface it so the
      holder can subscribe.
    • ["status", "registered"] — explicit; leaves room for
      unregistered / refunded / etc. via the same kind later.
  • Content: empty or short JSON {"ticket_id": "...", "ts": <unix>}. Don't include holder PII (name/email) since this is
    public.
  • Signed by: the organizer's pubkey (account.prvkey per the
    existing publish flow).

NIP landscape

No existing NIP covers ticket attendance directly (NIP-52 is calendar
events, NIP-99 is classified marketplace). Reasonable to define the
shape internally and propose upstream later if it sees traction.

Use cases unlocked

  • Ticket holder's client (future webapp feature) subscribes to
    {kinds: [30030], "#d": [<ticket_id>]} and shows a "✓ Checked in"
    badge when the attestation lands on relays.
  • Ecosystem ticket-verifier apps (third-party) can validate that an
    organizer flagged a specific ticket as scanned without trusting
    any LNbits instance.
  • Foundation for nostr-only event ticketing (when LNbits → Nostr
    RPC becomes the only transport).

Out of scope

  • Webapp consumption of the attestation. File a separate issue on
    aiolabs/webapp once this lands.
  • Cross-organizer attestation forwarding / multi-sig.
  • Revocation events (["status", "unregistered"]) — the kind is
    replaceable so the same d-tag re-publish handles a correction.
    Explicit revocation can come later.

Test plan

  • After a ticket registers (via either RPC or HTTP), a kind
    30030 event with the d-tag matching the ticket id appears
    on the connected relays, signed by the organizer's pubkey.
  • The event includes the a-tag to the calendar event and the
    status: registered tag.
  • No PII (name, email) in the content or tags.
  • Re-publishing (manually triggering the hook twice) replaces
    the existing attestation under the same d-tag on the
    relay (NIP-33 replaceable semantics).
  • Nostr publish failures are swallowed (logged, not raised) so
    a relay outage doesn't break the register flow.
## Context PR #19 added `events_ticket_register` over the LNbits nostr transport. The transport encrypts every RPC event (NIP-44 v2) to the server's transport pubkey, so the registration record is visible only to the organizer + the server. For ticket-holder-side verifiability — and as a stepping stone toward fully-Nostr-native scanning where the webapp publishes the attestation directly without a backend round-trip — the events extension should ALSO publish an unencrypted attestation event when a ticket flips to `registered`. ## Proposed shape Modeled on the existing `publish_or_delete_nostr_event` pattern in `nostr_hooks.py`, which signs and broadcasts on the organizer's keypair. New publish call site lives in `services.py` (or a dedicated `services.register_attestation`), invoked from BOTH register paths so the wire signal is consistent: 1. Inside `transport_rpcs.handle_events_ticket_register` — right after the `update_ticket(ticket)` line. 2. Inside the legacy HTTP `api_event_register_ticket` (views_api.py) — same hook for the LNbits admin Quasar UI. ### Event proposal - **Kind**: pick from a custom range (e.g. `30030` — addressable replaceable so the registrar can re-publish if they need to correct a bad scan; OR a regular kind in the 1000-9999 range if we want it ephemeral). Probably `30030` for replaceability. - **Tags**: - `["d", "<ticket_id>"]` — addressable identifier so the organizer can only have one "registered" attestation per ticket (re-publishes replace). - `["a", "<31922|31923>:<organizer_pubkey>:<event_d_tag>"]` — NIP-19 reference to the parent calendar event. - `["p", "<ticket_holder_pubkey>"]` — if the ticket has a `nostr_identifier` and it resolves to a pubkey, surface it so the holder can subscribe. - `["status", "registered"]` — explicit; leaves room for `unregistered` / `refunded` / etc. via the same kind later. - **Content**: empty or short JSON `{"ticket_id": "...", "ts": <unix>}`. Don't include holder PII (name/email) since this is public. - **Signed by**: the organizer's pubkey (`account.prvkey` per the existing publish flow). ## NIP landscape No existing NIP covers ticket attendance directly (NIP-52 is calendar events, NIP-99 is classified marketplace). Reasonable to define the shape internally and propose upstream later if it sees traction. ## Use cases unlocked - Ticket holder's client (future webapp feature) subscribes to `{kinds: [30030], "#d": [<ticket_id>]}` and shows a "✓ Checked in" badge when the attestation lands on relays. - Ecosystem ticket-verifier apps (third-party) can validate that an organizer flagged a specific ticket as scanned without trusting any LNbits instance. - Foundation for nostr-only event ticketing (when LNbits → Nostr RPC becomes the only transport). ## Out of scope - Webapp consumption of the attestation. File a separate issue on `aiolabs/webapp` once this lands. - Cross-organizer attestation forwarding / multi-sig. - Revocation events (`["status", "unregistered"]`) — the kind is replaceable so the same `d`-tag re-publish handles a correction. Explicit revocation can come later. ## Test plan - [ ] After a ticket registers (via either RPC or HTTP), a kind 30030 event with the `d`-tag matching the ticket id appears on the connected relays, signed by the organizer's pubkey. - [ ] The event includes the `a`-tag to the calendar event and the `status: registered` tag. - [ ] No PII (name, email) in the content or tags. - [ ] Re-publishing (manually triggering the hook twice) replaces the existing attestation under the same `d`-tag on the relay (NIP-33 replaceable semantics). - [ ] Nostr publish failures are swallowed (logged, not raised) so a relay outage doesn't break the register flow.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/events#20
No description provided.