Nostr Addressable Event d-tag Requirement

SkillMedia

Fix duplicate Nostr events (Kind 30000+ parameterized replaceable events) caused by missing d-tag. Use when: (1) Same content appears multiple times on relay for same pubkey, (2) Events aren't being replaced/updated as expected, (3) Publishing NIP-71 video events (Kind 34236) or other addressable events and seeing duplicates. The d-tag is REQUIRED for addressability - without it, each publish creates a new non-replaceable event.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Nostr Addressable Event d-tag Requirement skill

What this skill tells your AI

The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/nostr-addressable-event-d-tag-requirement/SKILL.md and read by ahel’s review.

Problem

When publishing parameterized replaceable events (Kind 30000-39999, including NIP-71 video events Kind 34236), events appear as duplicates on the relay instead of replacing each other. The same content shows up multiple times for the same user.

Context / Trigger Conditions

  • Publishing Kind 30000+ events (parameterized replaceable events)
  • Same video/content appears multiple times in user's feed
  • Events have identical pubkey, kind, and content but different event IDs
  • Relay returns multiple events when you expect one
  • Using NIP-71 (Kind 34236) for video events
  • Using NIP-23 (Kind 30023) for long-form content
  • Any addressable event type showing duplicates

Solution

Root Cause

Parameterized replaceable events (Kind 30000-39999) require a d tag to be addressable. The combination of pubkey + kind + d-tag value forms the unique address. Without a d tag, the relay treats each event as a separate non-replaceable event.

Fix

Always include a d tag with a unique identifier for the content:

// CORRECT - has d tag, will be replaceable
const event = {
  kind: 34236, // NIP-71 video
  pubkey: userPubkey,
  content: "Video description",
  tags: [
    ["d", videoId],  // REQUIRED for addressability
    ["title", "My Video"],
    ["url", "https://..."],
    // ... other tags
  ]
};

// WRONG - missing d tag, creates new event each time
const badEvent = {
  kind: 34236,
  pubkey: userPubkey,
  content: "Video description",
  tags: [
    ["title", "My Video"],  // No d tag!
    ["url", "https://..."],
  ]
};

For NIP-71 Video Events

Use the video's unique identifier (vine_id, video_id, etc.) as the d-tag value:

tags.push(["d", video.id]);

Cleaning Up Existing Duplicates

  1. Fetch all events for the pubkey: { kinds: [34236], authors: [pubkey] }
  2. Identify events without d-tags (these are the duplicates)
  3. Publish NIP-09 deletion events (Kind 5) referencing the bad event IDs
  4. Republish with proper d-tags

Verification

After adding the d-tag:

  1. Publish the same content twice
  2. Query the relay for events with that pubkey+kind
  3. Should return only ONE event (the latest)
  4. Event ID will change but d-tag address remains constant
// Verify no duplicates
const events = await ndk.fetchEvents({ kinds: [34236], authors: [pubkey] });
const byDTag = new Map();
for (const e of events) {
  const d = e.tags.find(t => t[0] === 'd')?.[1];
  if (!byDTag.has(d)) byDTag.set(d, []);
  byDTag.get(d).push(e);
}
// Each d-tag value should have exactly 1 event
for (const [d, evts] of byDTag) {
  if (evts.length > 1) console.log(`Duplicate: d=${d} has ${evts.length} events`);
}

Example

Before fix - events without d-tag create duplicates:

Event 1: id=abc123, kind=34236, tags=[["title", "My Video"]]
Event 2: id=def456, kind=34236, tags=[["title", "My Video"]]  // Duplicate!

After fix - events with d-tag replace each other:

Event 1: id=abc123, kind=34236, tags=[["d", "video-001"], ["title", "My Video"]]
// Republishing same content...
Event 2: id=xyz789, kind=34236, tags=[["d", "video-001"], ["title", "My Video Updated"]]
// Event 1 is replaced, only Event 2 exists

Notes

  • The d-tag value should be stable across republishes (use content ID, not random)
  • Empty string ["d", ""] is valid but means only one event of that kind per pubkey
  • Regular replaceable events (Kind 0, 3, 10000-19999) don't need d-tag
  • This applies to ALL parameterized replaceable events, not just videos
  • Frontend caching may show old duplicates even after relay is cleaned - hard refresh

References

Signals

GitHub stars
265
Forks
55
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
nostr-addressable-event-d-tag-requirement
Source
github.com/divinevideo/divine-mobile