NIP-09: Event Deletion Request

NIP-09 defines a best-effort mechanism for retracting events you previously published. It does not guarantee erasure—relays and downstream clients are allowed to cache or ignore deletion requests—so treat it as a courtesy protocol for well-behaved peers.

When to send a deletion event

  1. You control the secret key that signed the original event.
  2. You know the event IDs (or coordinates for replaceable/parameterized events) you want to retract.
  3. You accept that the payload remains public even after the deletion request propagates.

Building the request

Rust
// Construct the request
let request = EventDeletionRequest::new()
    .id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?)
    // optionally add coordinates for replaceable events
    // .coordinate(...)
    .reason("these posts were published by accident");

// Build the event
let event: Event = EventBuilder::delete(request).sign_with_keys(&keys)?;
Python
# Construct the request
id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
request = EventDeletionRequest(ids=[id], coordinates=[], reason="these posts were published by accident")

# Build the event
event: Event = EventBuilder.delete(request).sign_with_keys(keys)
JavaScript
// Construct the request
let id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96");
let request = new EventDeletionRequest();
request.ids = [id];
request.reason = "these posts were published by accident";


// Build the event
let event = EventBuilder.delete(request).signWithKeys(keys);
Kotlin
// Construct the request
val id = EventId.parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
val request = EventDeletionRequest(ids = listOf(id), coordinates = listOf(), reason = "these posts were published by accident")

// Build the event
val event: Event = EventBuilder.delete(request).signWithKeys(keys)
Swift
// Construct the request
let id = try EventId.parse(id: "7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")
let request = EventDeletionRequest(ids: [id], coordinates: [], reason: "these posts were published by accident")

// Build the event
let builder = EventBuilder.delete(request: request)
let event = try builder.signWithKeys(keys: keys)
C#
// Construct the request
var id = EventId.Parse("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96");
var request = new EventDeletionRequest(ids: [id], coordinates: [], reason: "these posts were published by accident");

// Build the event
Event e = EventBuilder.Delete(request).SignWithKeys(keys);
Flutter

TODO

Then, the deletion event can be sent to every relay that received the original event.

Caveats

  • Relays are free to ignore deletion events entirely or only apply them to new subscribers.
  • Archive relays and scrapers can continue serving the old content indefinitely.
  • Deleting a parameterized replaceable event without its coordinate will have no effect.

In short: use NIP-09 as part of your UX, but do not treat it as a hard delete.