§ 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
npmnpm install @nuvouch/integrator-sdkbootstrap.ts
from envimport { 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
sdkimport {
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
SDKconst 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 | cancelledFetch 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
| Field | Type | Description |
|---|---|---|
| NuvouchApiError | error class | Thrown for all non-2xx responses. |
| status | number | HTTP status from the API response. |
| code | string | Stable machine-readable Nuvouch error code when provided. |
| retryAfterSeconds | number | Parsed 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.