Quickstart

Get from zero to receiving encrypted webhooks in under five minutes. This guide walks you through creating an account, generating encryption keys, setting up a webhook endpoint, and consuming messages.

The 30-second version

If you already have an account and keys, here are the three commands that matter:

# 1. Create an API key
curl -X POST https://nexus-broker.dev/api/keys \
  -H "Authorization: Bearer <firebase-id-token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-key"}'

# 2. Create a webhook with your public key
curl -X POST https://nexus-broker.dev/api/webhooks \
  -H "x-api-key: nxb_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "stripe-webhooks",
    "publicKey": "'"$(cat public.pem)"'"
  }'

# 3. Dequeue and decrypt
curl https://nexus-broker.dev/api/webhooks/<id>/dequeue \
  -H "x-api-key: nxb_your_api_key"

Step 1 — Create an account

Navigate to the /dashboard page and sign in with your Google or GitHub account. Nexus Broker uses Firebase Authentication, so there are no passwords to manage. After signing in you'll land on your dashboard where you can manage API keys, webhooks, and view usage statistics.

Step 2 — Generate a keypair

Nexus Broker encrypts every webhook payload with your public key before storing it. Only someone with the corresponding private key can decrypt the contents. You choose between two algorithms:

  • RSA-2048 — The industry standard. Widely compatible, supported by every crypto library. No forward secrecy.
  • X25519 — Modern elliptic curve. Smaller keys, faster operations, provides forward secrecy through ephemeral key exchange. Recommended for new projects.

Generate your keypair locally using OpenSSL. Never upload your private key.

RSA-2048

# Generate a 2048-bit RSA private key
openssl genrsa -out private.pem 2048

# Extract the public key
openssl rsa -in private.pem -pubout -out public.pem

X25519

# Generate an X25519 private key
openssl genpkey -algorithm X25519 -out private.pem

# Extract the public key
openssl pkey -in private.pem -pubout -out public.pem

Tip: Store your private key securely. If you lose it, you lose access to all encrypted messages. Consider using a secrets manager like AWS Secrets Manager, HashiCorp Vault, or a hardware security module for production workloads.

Step 3 — Create an API key

API keys authenticate requests to the Nexus Broker management and queue APIs. You can create them from the dashboard or programmatically:

curl -X POST https://nexus-broker.dev/api/keys \
  -H "Authorization: Bearer <firebase-id-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-key"
  }'

The response returns your API key prefixed with nxb_. Store it securely — it won't be shown again.

{
  "id": "key_abc123",
  "name": "production-key",
  "key": "nxb_Kj3m9x...Rq7w",
  "createdAt": "2025-01-15T10:30:00Z"
}

Step 4 — Create a webhook

A webhook represents an ingestion endpoint. When you create one, Nexus Broker generates a unique slug that external services can POST to. You'll provide your public key so payloads get encrypted on arrival.

curl -X POST https://nexus-broker.dev/api/webhooks \
  -H "x-api-key: nxb_Kj3m9x...Rq7w" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "stripe-payments",
    "publicKey": "'"$(cat public.pem)"'",
    "algorithm": "RSA-2048"
  }'
{
  "id": "wh_def456",
  "name": "stripe-payments",
  "slug": "a7b3c9f2e1",
  "url": "https://nexus-broker.dev/wh/a7b3c9f2e1",
  "algorithm": "RSA-2048",
  "createdAt": "2025-01-15T10:35:00Z"
}

The url field is the endpoint you give to external services. Any HTTP POST to this URL will be encrypted and queued.

Step 5 — Send a webhook

Send any JSON payload to your webhook URL. The ingestion endpoint is public and unauthenticated — it accepts any valid JSON body.

curl -X POST https://nexus-broker.dev/wh/a7b3c9f2e1 \
  -H "Content-Type: application/json" \
  -d '{
    "event": "payment.completed",
    "amount": 4999,
    "currency": "usd",
    "customer": "cus_abc123"
  }'
{
  "id": "msg_ghi789",
  "sequence": 1,
  "status": "queued",
  "createdAt": "2025-01-15T10:40:00Z"
}

Step 6 — Consume messages

Use the dequeue endpoint to fetch and lock the next available message. The response includes the encrypted payload, which you decrypt with your private key.

curl -X POST https://nexus-broker.dev/api/webhooks/wh_def456/dequeue \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"
{
  "message": {
    "id": "msg_ghi789",
    "sequence": 1,
    "status": "locked",
    "lockExpiresAt": "2025-01-15T10:45:00Z",
    "createdAt": "2025-01-15T10:40:00Z"
  },
  "encrypted": {
    "ciphertext": "base64-encoded-ciphertext...",
    "iv": "base64-encoded-iv",
    "tag": "base64-encoded-tag",
    "encryptedKey": "base64-encoded-encrypted-aes-key",
    "algorithm": "RSA-2048-AES-256-GCM"
  },
  "headers": {
    "content-type": "application/json"
  }
}

Decrypting in Node.js

import { privateDecrypt, createDecipheriv } from "node:crypto";
import { readFileSync } from "node:fs";

const privateKey = readFileSync("private.pem", "utf-8");

function decrypt(encrypted) {
  // 1. Decrypt the AES key with RSA private key
  const aesKey = privateDecrypt(
    { key: privateKey, padding: 1, oaepHash: "sha256" },
    Buffer.from(encrypted.encryptedKey, "base64"),
  );

  // 2. Decrypt the payload with AES-256-GCM
  const decipher = createDecipheriv(
    "aes-256-gcm",
    aesKey,
    Buffer.from(encrypted.iv, "base64"),
  );
  decipher.setAuthTag(Buffer.from(encrypted.tag, "base64"));

  const decrypted = Buffer.concat([
    decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
    decipher.final(),
  ]);

  return JSON.parse(decrypted.toString("utf-8"));
}

Decrypting in Python

import base64, json
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes

with open("private.pem", "rb") as f:
    private_key = load_pem_private_key(f.read(), password=None)

def decrypt(encrypted):
    # 1. Decrypt the AES key
    aes_key = private_key.decrypt(
        base64.b64decode(encrypted["encryptedKey"]),
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        ),
    )

    # 2. Decrypt the payload with AES-256-GCM
    aesgcm = AESGCM(aes_key)
    plaintext = aesgcm.decrypt(
        nonce=base64.b64decode(encrypted["iv"]),
        data=(
            base64.b64decode(encrypted["ciphertext"])
            + base64.b64decode(encrypted["tag"])
        ),
    )
    return json.loads(plaintext)

Step 7 — Commit or error

After successfully processing a message, commit it to remove it from the queue:

# Acknowledge successful processing
curl -X POST https://nexus-broker.dev/api/webhooks/wh_def456/messages/msg_ghi789/commit \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"

If processing fails, report the error. The message will be released back to the queue for retry (up to the maximum retry count):

# Report a processing failure
curl -X POST https://nexus-broker.dev/api/webhooks/wh_def456/messages/msg_ghi789/error \
  -H "x-api-key: nxb_Kj3m9x...Rq7w" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Database connection timeout"}'

Warning: If you neither commit nor error a message, its lock will expire after the configured timeout (default 300 seconds). The message will return to the queue and may be dequeued again, leading to duplicate processing. Always commit or error every message you dequeue.

Next steps