louislam/uptime-kuma · error · Error

Failed to decode private key for sender ${sender}: ${error.m

Error message

Failed to decode private key for sender ${sender}: ${error.message}

What it means

Thrown by Nostr.getPrivateKey when nip19.decode(sender) fails. The sender is expected to be a bech32-encoded private key (nsec1...). If decode rejects (invalid bech32, wrong prefix, malformed string), the provider rethrows as 'Failed to decode private key for sender <sender>: <inner.message>'. The returned data is then used directly as the private key for signing/sealing.

Source

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

        // Report success or failure
        if (successfulRelays === 0) {
            throw Error("Failed to connect to any relays.");
        }
        return `${successfulRelays}/${relays.length} relays connected.`;
    }

    /**
     * Get the private key for the sender
     * @param {string} sender Sender to retrieve key for
     * @returns {nip19.DecodeResult} Private key
     */
    async getPrivateKey(sender) {
        try {
            const senderDecodeResult = await nip19.decode(sender);
            const { data } = senderDecodeResult;
            return data;
        } catch (error) {
            throw new Error(`Failed to decode private key for sender ${sender}: ${error.message}`);
        }
    }

    /**
     * Get public keys for recipients
     * @param {string} recipients Newline delimited list of recipients
     * @returns {Promise<nip19.DecodeResult[]>} Public keys
     */
    async getPublicKeys(recipients) {
        const recipientsList = recipients.split("\n");
        const publicKeys = [];
        for (const recipient of recipientsList) {
            try {
                const recipientDecodeResult = await nip19.decode(recipient);
                const { type, data } = recipientDecodeResult;
                if (type === "npub") {
                    publicKeys.push(data);
                } else {

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Generate or obtain the sender's private key in nsec form (nsec1...) from your Nostr client and paste that.
  2. Trim whitespace/newlines from notification.sender before saving.
  3. Do not use an npub here — npub is public-only and cannot sign.
  4. Verify the nsec decodes in isolation with nip19.decode to confirm it is well-formed.

Example fix

// before
const senderDecodeResult = await nip19.decode(sender);

// after - normalize input and give a clearer failure
const trimmed = String(sender || "").trim();
if (!trimmed.startsWith("nsec1")) {
    throw new Error(`Sender must be an nsec private key, got: ${trimmed.slice(0, 10)}...`);
}
const senderDecodeResult = await nip19.decode(trimmed);
Defensive patterns

Strategy: validation

Validate before calling

const sender = String(notification.sender || "").trim();
if (!sender.startsWith("nsec1")) {
    throw new Error("Sender must be an nsec1... private key");
}

Type guard

/** @param {string} s */
function looksLikeNsec(s) {
    return typeof s === "string" && s.startsWith("nsec1") && s.length > 10;
}

Try / catch

try {
    return (await nip19.decode(sender)).data;
} catch (error) {
    throw new Error(`Sender is not a valid nsec private key: ${error.message}`);
}

Prevention

When it happens

Trigger: notification.sender is empty, is a raw hex string (not nsec-prefixed), is an npub (public key), has a typo breaking bech32 checksum, or is an nsec from an incompatible nostr-tools version.

Common situations: User pasted a public key (npub) into the sender/private-key field; pasted a hex key; copied an nsec with leading/trailing whitespace or a missing character that breaks the checksum.

Understand the failure class

Related errors


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