louislam/uptime-kuma · error · Error

Failed to create gift-wrapped event for recipient

Error message

Failed to create gift-wrapped event for recipient: ${error.message}

What it means

Inside the Nostr provider's per-recipient loop, nip59.wrapEvent() is called to build a NIP-59 gift-wrapped (sealed + rumor) event from a kind-14 NIP-17 private direct message. If wrapEvent throws (bad key types, malformed event, or a nostr-tools internal failure), the provider wraps it as 'Failed to create gift-wrapped event for recipient: <inner.message>'. This is a cryptographic/format failure, not a network failure.

Solutions

  1. Ensure notification.sender is the sender's nsec private key so getPrivateKey returns the correct decoded bytes.
  2. Ensure recipients are npub values so getPublicKeys returns valid 32-byte public keys.
  3. Pin/upgrade nostr-tools to a version compatible with the NIP-59 API used here and check wrapEvent's signature expectations.
  4. Reproduce wrapEvent in isolation for one recipient to capture the precise inner error.
Defensive patterns

Strategy: type-guard

Validate before calling

const senderKey = await this.getPrivateKey(notification.sender); // throws early with a clear message
for (const r of recipientsPublicKeys) {
    if (!(r instanceof Uint8Array) && typeof r !== "string") {
        throw new Error(`Recipient key has invalid type: ${typeof r}`);
    }
}

Type guard

/** @param {unknown} k */
function isNostrKeyBytes(k) {
    return k instanceof Uint8Array && k.length === 32 ||
        (typeof k === "string" && /^[0-9a-fA-F]{64}$/.test(k));
}

Try / catch

try {
    const wrapped = nip59.wrapEvent(event, senderPrivateKey, recipientPublicKey);
    events.push(wrapped);
} catch (error) {
    throw new Error(`wrapEvent failed for recipient (key type ${typeof recipientPublicKey}): ${error.message}`);
}

Prevention

When it happens

Trigger: senderPrivateKey is not the hex/bytes shape nostr-tools expects (e.g. it was fed an npub or a string instead of decoded key data), recipientPublicKey is not valid bytes/hex for the seal recipient, or the event payload (tags/content/created_at) is malformed in a way nip59 rejects.

Common situations: Sender field entered as an npub (public) instead of an nsec (private); recipient public keys decoded but the decode returned unexpected data type; nostr-tools version change altering wrapEvent's accepted inputs.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/f4d6aca53232ad92. Report an issue: GitHub.

Appendix: source

Thrown at server/notification-providers/nostr.js:33

        const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);

        // Create NIP-59 gift-wrapped events for each recipient
        // This uses NIP-17 kind 14 (private direct message) wrapped with NIP-59
        // to prevent metadata leakage (sender/recipient public keys are hidden)
        const createdAt = Math.floor(Date.now() / 1000);
        const events = [];
        for (const recipientPublicKey of recipientsPublicKeys) {
            const event = {
                kind: 14, // NIP-17 private direct message
                created_at: createdAt,
                tags: [["p", recipientPublicKey]],
                content: msg,
            };
            try {
                const wrappedEvent = nip59.wrapEvent(event, senderPrivateKey, recipientPublicKey);
                events.push(wrappedEvent);
            } catch (error) {
                throw new Error(`Failed to create gift-wrapped event for recipient: ${error.message}`);
            }
        }

        // Publish events to each relay
        const relays = notification.relays.split("\n");
        let successfulRelays = 0;
        for (const relayUrl of relays) {
            const relay = await Relay.connect(relayUrl);
            let eventIndex = 0;

            // Authenticate to the relay, if required
            try {
                await relay.publish(events[0]);
                eventIndex = 1;
            } catch (error) {
                if (relay.challenge) {
                    await relay.auth(async (evt) => {
                        return finalizeEvent(evt, senderPrivateKey);

View on GitHub (pinned to 6b5ea01557)