n1coePayDocs API reference

Node.js SDK

@n1co/epay calls the ePay API from your Node.js server, with TypeScript types, coded errors and webhook verification.

  1. Install @n1co/epay on your server (Node.js 22.12+, 24 or 26+).
  2. Create the client with your secret key and the development host.
  3. Create intents with paymentIntents.create() and verify webhooks with constructEvent().
npm install @n1co/epay

A complete Express server: it creates the intent and receives the webhook.

server.mjs
import express from 'express';
import { N1co, WebhookSignatureError } from '@n1co/epay';

const n1co = new N1co(process.env.N1CO_SECRET_KEY, {
  baseUrl: 'https://api-epay.dev.n1co.dev', // development
});
const app = express();

// The webhook goes before express.json(): it needs the raw body.
app.post('/webhooks/n1co', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = N1co.webhooks.constructEvent(req.body, req.headers, process.env.N1CO_WEBHOOK_SECRET);
  } catch (err) {
    if (err instanceof WebhookSignatureError) return res.status(400).send('invalid signature');
    throw err;
  }
  if (event.type === 'payment_intent.succeeded') {
    // Fulfill the order in event.data.object.reference; skip repeated event.id values.
  }
  res.sendStatus(204);
});

app.use(express.json());

app.post('/checkout/payment-intent', async (req, res) => {
  // The amount comes from your order, never from the browser.
  const intent = await n1co.paymentIntents.create({
    amount: 1250,
    currency: 'USD',
    customer: { id: 'cust-1001', name: 'Ana López', email: 'ana.lopez@example.com', phone: '+503 7000-1234' },
    reference: 'order-8812',
  });
  res.json({ client_secret: intent.client_secret });
});

app.listen(3000);

The package ships ES modules and CommonJS, with no dependencies: const { N1co } = require('@n1co/epay');. The page confirms the payment as in Charge.

Webhooks with NestJS#

Turn on rawBody when you create the app and verify req.rawBody:

webhooks.controller.ts
// main.ts: const app = await NestFactory.create(AppModule, { rawBody: true });
import { BadRequestException, Controller, Headers, HttpCode, Post, Req } from '@nestjs/common';
import type { RawBodyRequest } from '@nestjs/common';
import type { Request } from 'express';
import { N1co, WebhookSignatureError } from '@n1co/epay';

@Controller('webhooks')
export class WebhooksController {
  @Post('n1co')
  @HttpCode(204)
  handle(@Req() req: RawBodyRequest<Request>, @Headers() headers: Record<string, string>) {
    try {
      const event = N1co.webhooks.constructEvent(req.rawBody!, headers, process.env.N1CO_WEBHOOK_SECRET!);
      // Handle event.type…
    } catch (err) {
      if (err instanceof WebhookSignatureError) throw new BadRequestException();
      throw err;
    }
  }
}

Errors#

Every SDK error has code (the API's stable code), status, details and requestId. Branch on code, never on the message.

import { isN1coError } from '@n1co/epay';

try {
  await n1co.paymentIntents.create(params);
} catch (err) {
  if (!isN1coError(err)) throw err;
  if (err.status === 400) console.log(err.details?.field, err.details?.code);
  else console.log(err.code, err.status, err.requestId);
}

The codes are in Errors. Include the requestId when you contact n1co.

Common mistakes#

  • Parsing the body before verifying. With express.json() on the webhook route, the signature does not match. Use express.raw() or rawBody: true.
  • Retrying a POST blindly. The SDK never retries a POST: the API has no idempotency keys. After a ConnectionError, read the intent or wait for its webhook before creating another one.
  • instanceof with both ESM and CommonJS. If your app loads both copies, use isN1coError(err) with code and status.
  • Leaving out baseUrl. Without it, the client uses the production host, which arrives with the production release.

The client options, methods, types and GET retries are in the package README.