Skip to main content
npayload is launching soon.
npayloadDocs

Quickstart

Publish your first message in five minutes

Get your first message published and delivered through npayload in five minutes.

What you will build

Before writing any code, here is the full picture of what happens when you publish a message through npayload:

The diagram above shows the complete event lifecycle:

  1. Your app publishes a message using the @npayload/node SDK.
  2. The edge layer authenticates your request with OAuth 2.0 + DPoP and applies rate limiting.
  3. The message is routed to a channel, a named stream that organises your events.
  4. The message is stored immutably with encryption and an audit trail.
  5. The fan-out engine routes the message to every matching subscription.
  6. Each subscription receives the message via webhook delivery with automatic retries.
  7. If delivery fails after all retries, the message goes to the dead letter queue for inspection and replay.

By the end of this quickstart, you will have all of this running.

Prerequisites

Get your credentials

  1. Log in to admin.npayload.com
  2. Create or select an Organisation
  3. Create an App (for example, "my-first-app")
  4. Navigate to Machine Credentials and register a new client
  5. Copy your Client ID (starts with oac_) and HMAC Secret

Never expose your HMAC secret in client side code or public repositories. Store it in a secret vault such as AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault.

Install the SDK

npm install @npayload/node
pnpm add @npayload/node
yarn add @npayload/node
bun add @npayload/node

Initialise the client

The SDK handles the full OAuth 2.0 + DPoP flow automatically. You provide your credentials; the SDK manages token exchange, refresh, and proof-of-possession signing.

import { NPayloadAuth, NPayloadClient } from '@npayload/node';

const auth = new NPayloadAuth({
  clientId: process.env.NPAYLOAD_CLIENT_ID!,   // oac_...
  hmacSecret: process.env.NPAYLOAD_HMAC_SECRET!,
});

const npayload = new NPayloadClient({
  auth,
  environment: 'development',
});

This corresponds to the Your app and Edge layer in the architecture diagram above. Every API call from this point forward is authenticated and rate-limited at the edge before reaching your instance.

Create a channel

A channel is a named message stream. All messages published to a channel are stored immutably and delivered to every subscription.

const channel = await npayload.channels.create({
  name: 'orders',
  description: 'Order events for my app',
  privacy: 'standard',  // 'standard' | 'hybrid' | 'e2e'
});

console.log('Channel created:', channel.gid);

npayload supports three privacy modes:

  • Standard: plaintext (fastest, simplest)
  • Hybrid: metadata visible, payload encrypted
  • E2E: zero-knowledge encryption where npayload cannot read your data

Publish a message

When you publish, the message is stored immutably and the fan-out engine routes it to all matching subscriptions.

const message = await npayload.messages.publish({
  channel: 'orders',
  payload: {
    event: 'order.created',
    orderId: 'ord_12345',
    customer: { id: 'cust_abc', email: 'customer@example.com' },
    total: 59.98,
    createdAt: new Date().toISOString(),
  },
});

console.log('Message published:', message.gid);

Create a subscription

A subscription tells npayload where to deliver messages. You can create multiple subscriptions per channel; each one receives every message independently.

const subscription = await npayload.subscriptions.create({
  channel: 'orders',
  name: 'fulfillment-webhook',
  type: 'webhook',
  endpoint: {
    url: 'https://your-service.com/webhooks/orders',
    method: 'POST',
  },
});

console.log('Subscription created:', subscription.gid);

Receive and verify webhooks

Set up an endpoint to receive messages. Always verify the HMAC signature before processing.

import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks/orders', (req, res) => {
  // Verify the webhook signature
  const isValid = npayload.webhooks.verify(
    req.body,
    req.headers['x-npayload-signature'] as string,
    process.env.WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the message
  const { payload } = req.body;
  console.log('Received event:', payload.event);
  console.log('Order ID:', payload.orderId);

  // Acknowledge receipt immediately
  res.status(200).json({ received: true });
});

app.listen(3000);

Return 200 immediately and process the message asynchronously. npayload retries on non-2xx responses with exponential backoff (1s, 30s, 5m, 30m, 2h). After all retries are exhausted, the delivery is moved to the dead letter queue.

The delivery lifecycle

Here is what npayload does behind the scenes every time you publish:

StageWhat happens
PublishYour message is validated, encrypted (if applicable), and stored immutably
Fan-outThe message is routed to every subscription matching the channel
DeliverEach subscription endpoint receives an HTTP POST with retries on failure
WebhookOn success, delivery is marked complete with a full audit trail
DLQOn failure after all retries, the message goes to the dead letter queue for inspection, debugging, and replay

Complete example

Here is everything together in a single script:

import { NPayloadAuth, NPayloadClient } from '@npayload/node';

async function main() {
  const auth = new NPayloadAuth({
    clientId: process.env.NPAYLOAD_CLIENT_ID!,
    hmacSecret: process.env.NPAYLOAD_HMAC_SECRET!,
  });

  const npayload = new NPayloadClient({
    auth,
    environment: 'development',
  });

  // Create a channel
  const channel = await npayload.channels.create({
    name: 'notifications',
    description: 'User notifications',
  });

  // Create a subscription
  await npayload.subscriptions.create({
    channel: 'notifications',
    name: 'email-service',
    type: 'webhook',
    endpoint: {
      url: 'https://email-service.example.com/notify',
    },
  });

  // Publish messages
  for (let i = 0; i < 3; i++) {
    const message = await npayload.messages.publish({
      channel: 'notifications',
      payload: {
        type: 'welcome',
        userId: `user_${i}`,
        message: 'Welcome to npayload!',
      },
    });
    console.log(`Published message ${i + 1}:`, message.gid);
  }

  console.log('Done. Check your webhook endpoint for deliveries.');
}

main().catch(console.error);

Troubleshooting

ProblemSolution
"Invalid credentials"Ensure your client ID starts with oac_ and the HMAC secret matches
"Channel not found"Create the channel first with channels.create()
Webhook not receivingVerify the URL is publicly accessible and uses HTTPS

Next steps

Was this page helpful?

On this page