§ SDK · Node.js

Integrate with @nuvouch/integrator-sdk

Use the official SDK when you want typed payloads, predictable error objects, and webhook utilities without manually wiring request signing or parsing.

install
npm
npm install @nuvouch/integrator-sdk
bootstrap.ts
from env
import { createNuvouchIntegratorClientFromEnv } from "@nuvouch/integrator-sdk";

const client = createNuvouchIntegratorClientFromEnv();
// reads NUVOUCH_INTEGRATOR_API_KEY
// optional: NUVOUCH_API_BASE_URL
§ SDK · Connections

Link business subjects once, then reuse them

New integrations should prefer targetSubject over storing raw Nuvouch user ids. Link the subject once in your business product, then reuse that same subjectId and contextKey tuple for future approval requests.

create-connection-session.ts
sdk
import {
  createNuvouchIntegratorClientFromEnv,
  isConnectionAlreadyLinkedResponse,
  renderConnectionSessionQrDataUrl,
} from "@nuvouch/integrator-sdk";

const client = createNuvouchIntegratorClientFromEnv();

const session = await client.createConnectionSession({
  subjectId: "cus_123",
  subjectLabel: "Ada Lovelace",
  contextKey: "merchant:acct_live_001",
  contextType: "merchant",
  contextLabel: "Stripe Live Account",
});

if (isConnectionAlreadyLinkedResponse(session)) {
  console.log("already linked", session.connection.id);
} else {
  const qrDataUrl = await renderConnectionSessionQrDataUrl(session, {
    width: 320,
    type: "image/png",
  });

  console.log(session.sessionId, session.shortCode, qrDataUrl);
}
§ SDK · Connection lifecycle

Inspect and revoke existing connections

Fetch session state

get-connection-session.ts
const result = await client.getConnectionSession("conn_sess_123");
console.log(result.session.status);
console.log(result.session.connection?.id);

Lookup the active link for a subject tuple

lookup-connection.ts
const result = await client.lookupConnection({
  subjectId: "cus_123",
  contextKey: "merchant:acct_live_001",
});

console.log(result.connection.nuvouchUserId);

List connections with filters

list-connections.ts
const result = await client.listConnections({
  subjectId: "cus_123",
  status: "active",
});

console.log(result.items.map((item) => item.id));

Revoke a link

revoke-connection.ts
const result = await client.revokeConnection("conn_123");
console.log(result.connection.status); // revoked
§ SDK · Approvals

Create a payment authorization request

POST/api/nuvouch/v1/integrator/approval-requests
create-approval.ts
SDK
const approval = await client.createApprovalRequest({
  targetSubject: {
    subjectId: "cus_123",
    contextKey: "merchant:acct_live_001"
  },
  externalRequestId: "payment_auth_001",
  title: "Approve payment",
  summary: "Stripe needs the customer to confirm a high-risk payment.",
  requestedFor: "Payment authorization",
  actor: {
    id: "payments_agent_01",
    name: "Stripe Payment Agent",
    subtitle: "Payments risk workflow",
    avatarLabel: "ST"
  },
  context: {
    kind: "digital-service",
    title: "Card payment",
    location: "Stripe",
    reason: "High-risk payment requires customer confirmation",
    expiresAt: "2026-04-21T18:00:00.000Z",
    referenceCode: "pi_123"
  },
  risk: {
    level: "high",
    summary: "Customer confirmation required before capture",
    checks: ["3DS fallback unavailable", "Risk score above threshold"]
  },
  actions: [
    { label: "Approve payment", value: "approve" },
    { label: "Deny payment", value: "deny" }
  ],
  amount: "$84.00"
});
§ SDK · Request lifecycle

Read or cancel request state

Fetch by request id

get.ts
const current = await client.getApprovalRequest("req_2f5cd6dbb6784b9ab2f7");
console.log(current.approvalRequest.status); // pending | approved | denied | expired | cancelled

Fetch by your external id

get-by-external.ts
const current = await client.getApprovalRequestByExternalId("payment_auth_001");

Cancel a pending request

cancel.ts
const cancelled = await client.cancelApprovalRequest("req_2f5cd6dbb6784b9ab2f7");
§ SDK · Reliability

Error and retry behavior

FieldTypeDescription
NuvouchApiErrorerror classThrown for all non-2xx responses.
statusnumberHTTP status from the API response.
codestringStable machine-readable Nuvouch error code when provided.
retryAfterSecondsnumberParsed from Retry-After for rate-limited requests.
error-handling.ts
import {
  NUVOUCH_INTEGRATOR_ERROR_CODES,
  NuvouchApiError,
  isNuvouchRateLimitError,
  isNonRetryableNuvouchApprovalError,
} from "@nuvouch/integrator-sdk";

const input = {
  targetSubject: {
    subjectId: "cus_123",
    contextKey: "merchant:acct_live_001",
  },
  externalRequestId: "payment_auth_001",
  title: "Approve payment",
  summary: "Stripe needs the customer to confirm a high-risk payment.",
  requestedFor: "Payment authorization",
  actor: {
    id: "payments_agent_01",
    name: "Stripe Payment Agent",
    subtitle: "Payments risk workflow",
    avatarLabel: "ST",
  },
  context: {
    kind: "digital-service",
    title: "Card payment",
    reason: "High-risk payment requires customer confirmation",
    expiresAt: "2026-04-21T18:00:00.000Z",
    referenceCode: "pi_123",
  },
  risk: {
    level: "high",
    summary: "Customer confirmation required before capture",
  },
};

try {
  await client.createApprovalRequest(input, {
    idempotencyKey: "worker_retry_payment_auth_001",
  });
} catch (error) {
  if (isNuvouchRateLimitError(error)) {
    return;
  }
  if (
    error instanceof NuvouchApiError &&
    error.code === NUVOUCH_INTEGRATOR_ERROR_CODES.DUPLICATE_EXTERNAL_ID
  ) {
    return client.getApprovalRequestByExternalId(input.externalRequestId);
  }
  if (isNonRetryableNuvouchApprovalError(error)) {
    return;
  }
  if (error instanceof NuvouchApiError) {
    console.error(error.code, error.status, error.message);
  }
}
§ SDK · Webhooks

Verify signed callbacks

callback-handler.ts
import {
  createWebhookDeliveryTracker,
  parseNuvouchWebhookJson,
  verifyNuvouchWebhookSignatureFromEnv,
} from "@nuvouch/integrator-sdk";

const tracker = createWebhookDeliveryTracker({ maxEntries: 10_000 });

export async function handleNuvouchWebhook(req: Request) {
  const rawBody = await req.text();
  const valid = await verifyNuvouchWebhookSignatureFromEnv({
    rawBody,
    signatureHeader: req.headers.get("x-nuvouch-signature"),
  });
  if (!valid) return new Response("invalid signature", { status: 401 });

  const deliveryId = req.headers.get("x-nuvouch-delivery");
  if (deliveryId && !tracker.recordIfNew(deliveryId)) {
    return new Response("duplicate", { status: 200 });
  }

  const event = parseNuvouchWebhookJson(rawBody);
  if (event.kind === "approval" && event.body.payload.status === "approved") {
    // commit payment capture side effects exactly once
  }
  if (event.kind === "connection" && event.body.eventType === "nuvouch.connection.accepted") {
    // mark subject tuple as linked in your system
  }
  return new Response("ok");
}

Use a secret manager instead of env vars

callback-handler-secret-manager.ts
import {
  parseNuvouchWebhookJson,
  verifyNuvouchWebhookSignature,
} from "@nuvouch/integrator-sdk";

export async function handleNuvouchWebhook(req: Request) {
  const rawBody = await req.text();
  const callbackSecret = await secrets.get("integrations/nuvouch/callback-secret");

  const valid = await verifyNuvouchWebhookSignature({
    secret: callbackSecret,
    rawBody,
    signatureHeader: req.headers.get("x-nuvouch-signature"),
  });
  if (!valid) return new Response("invalid signature", { status: 401 });

  const event = parseNuvouchWebhookJson(rawBody);
  return new Response("ok");
}

Continue with the raw webhook contract on webhook reference.