> For the complete documentation index, see [llms.txt](https://docs.ecommerce.seb.ee/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ecommerce.seb.ee/integration/api-client-sdk/using-the-sdk/node.js-typescript.md).

# Node.js / TypeScript

Build Node.js payment integrations with clients, request DTOs, API calls, error handling, and webhooks.

Use this guide if you have completed the [Node.js quickstart](/integration/api-client-sdk/quickstarts/node.js-typescript.md) and want to build a full integration: create the client, compose request objects, call the API, handle failures, and process a **Webhook (also referred to as callback notifications)**. A [complete example](#complete-example) puts the steps together.

## Step 1 — Create the client

Everything starts with `DefaultEgwClientFactory`. Unlike the fluent Java and PHP factories, the Node.js factory takes a single configuration object, and derives nothing from the base URL — you supply the base URL, token endpoint and JWK endpoint explicitly:

```ts
import { DefaultEgwClientFactory } from "@tieto/egw-sdk";

const client = new DefaultEgwClientFactory().create({
  baseUri: new URL("https://sandbox.api.ecommerce.sebgroup.com"),
  authentication: {
    tokenEndpoint: new URL("https://api.ecommerce.sebgroup.com/auth/realms/participants-seb/protocol/openid-connect/token"),
    clientId: process.env.EGW_CLIENT_ID!,
    clientSecret: process.env.EGW_CLIENT_SECRET!,
  },
  jwkUri: new URL("https://checkout.ecommerce.sebgroup.com/api/keys/card-encryption.jwk"),
});
```

Importing the factory registers the default adapters (HTTP client, serializer, encrypter, silent logger) automatically, so the snippet above is complete. To replace an adapter — the usual route for timeouts, proxies or logging — call `registerProvider({...})` *before* `create()`; the factory snapshots the registered providers at creation time. Reaching `registerProvider` currently requires a deep import.

Create the client once at startup and reuse it — the OAuth token is cached and refreshed automatically. There is no listener to release in the Node.js SDK ([Step 5](#step-5--process-webhooks)); `dispose()` exists for symmetry but has nothing to stop in the current build.

## Step 2 — Compose request DTOs

Requests and responses are data transfer objects (DTOs): immutable classes with `readonly` properties, read directly (`response.paymentId`). Each request DTO has a companion builder class, available both as a named export (`AmountBuilder`) and as a static alias on the DTO (`new Amount.Builder()`).

**Classic builder.** Setters are camelCase, named after the property, and return the builder; `build()` produces the DTO:

```ts
import { Amount } from "@tieto/egw-sdk";

const amount = new Amount.Builder()
  .amount("100.00")
  .currency("EUR")
  .build();
```

**Nested lambda builder.** For every property that is itself a DTO, the builder offers a `<property>Builder(...)` variant taking a function over the nested builder — describe the nested object inline instead of building it separately. The function receives a fresh builder and must return it:

```ts
import { CitPaymentChannel, InitiateCitPaymentRequest } from "@tieto/egw-sdk";

const request = new InitiateCitPaymentRequest.Builder()
  .channel(CitPaymentChannel.Browser)
  .instructedAmountBuilder((a) => a.amount("100.00").currency("EUR"))
  .orderNumber("ORD-1001")
  .participantId(process.env.EGW_PARTICIPANT_ID!)
  .shopAgreementId(process.env.EGW_SHOP_AGREEMENT_ID!)
  .shopRedirectUrl("https://shop.example.com/return")
  .build();
```

**Singular item adders.** Array properties get an additional `<property>Item(...)` method that appends one element, so you can add items in a loop without managing the array yourself:

```ts
import { Basket } from "@tieto/egw-sdk";

const basket = new Basket.Builder()
  .itemsItem(rodItem)
  .itemsItem(reelItem)
  .subtotalAmountBuilder((a) => a.amount("100.00").currency("EUR"))
  .build();
```

**Validation.** The Node.js SDK does not check required fields at build time — unlike Java, a builder happily builds with required fields undefined, and the mistake surfaces as a server-side HTTP 400.

## Step 3 — Call the API

Operations are grouped by domain and reached through accessors on the client: `client.payments()`, `client.refunds()`, `client.tokens()`, `client.shop()`, and the checkout-session groups `client.cardSessions()`, `client.bankSessions()`, `client.tokenSessions()`, `client.clickToPaySessions()`.

```ts
import { randomUUID } from "node:crypto";

const payments = client.payments();
```

All operations are `async` and take a request ID string as their first argument. Generate one per call and keep it with your order record — it is your correlation handle for support.

Operations with a request body come in two forms: `<operation>(...)` taking a built DTO, and `<operation>WithBuilder(...)` taking a function over the request builder:

```ts
// With a prepared DTO:
const created = await payments.initiateCitPayment(randomUUID(), request);

// Or with the builder function inline (the CancelReason enum is affected by
// the open enum-serialization defect, like all enums on the wire):
const cancelled = await payments.cancelPaymentWithBuilder(
  randomUUID(),
  created.paymentId!,
  (b) => b.reason(CancelReason.SuspectedFraud)
);
```

Reading state and capturing after checkout completes:

```ts
const info = await payments.getPaymentInformation(randomUUID(), created.paymentId!);
console.log("Status:", info.paymentStatus);

const captured = await payments.capturePaymentWithBuilder(
  randomUUID(),
  created.paymentId!,
  (b) => b.instructedAmountBuilder((a) => a.amount("100.00").currency("EUR"))
);
```

Behind every call the SDK fetches and caches an OAuth 2.0 access token, sets the `X-Request-ID` header, serializes the request, and deserializes the response into the typed response DTO.

## Step 4 — Handle errors

All SDK failures extend `ECommException`, which extends `Error` and sets `name` to the concrete class name — `"AuthenticationException"`, `"ConnectionException"`, and so on — with the underlying error in `cause`. The exception classes are not exported from the package root in this build, so branch on `name` rather than `instanceof`:

```ts
try {
  const created = await payments.initiateCitPayment(randomUUID(), request);
} catch (e) {
  if (e instanceof Error) {
    switch (e.name) {
      case "AuthenticationException":
        // Token retrieval failed — check credentials and the token endpoint.
        break;
      case "ConnectionException":
        // Transport failure. The SDK never retries: query the payment state
        // with getPaymentInformation() before re-sending a state-changing call.
        break;
      default:
        // ECommException: the API rejected the call (status code is in the
        // message, e.g. "Client error during initiateCitPayment: 400") or the
        // response could not be processed.
        break;
    }
  }
}
```

The API's error details are not attached to the error in this build — enable a logger via `registerProvider` and correlate on `X-Request-ID`; see [Reading API errors](/integration/api-client-sdk/guides/error-handling.md#reading-api-errors). The full hierarchy is in [Error handling](/integration/api-client-sdk/guides/error-handling.md).

## Step 5 — Process Webhooks

The platform notifies you about payment state changes by POSTing to an endpoint you expose. Unlike the Java, PHP and C# SDKs, the Node.js SDK does not start a listener — you run the HTTP endpoint yourself (or reuse your existing Express/Fastify app) and use `parseWebhook()` to deserialize the payload:

```ts
import { createServer } from "node:http";

interface PaymentNotification {
  type: string;
  paymentId: string;
  paymentStatus: string;
}

const notifications = client.notifications();

createServer((req, res) => {
  const chunks: Buffer[] = [];
  req.on("data", (c) => chunks.push(c));
  req.on("end", async () => {
    res.writeHead(200).end(); // acknowledge first, work after

    const n = notifications.parseWebhook<PaymentNotification>(Buffer.concat(chunks));
    if (n.type === "PAYMENT") {
      const info = await payments.getPaymentInformation(randomUUID(), n.paymentId);
      // Update the order from info.paymentStatus — not from the notification alone.
    }
  });
}).listen(8080);
```

`parseWebhook<T>()` is generic — you declare the payload shape, as with `PaymentNotification` above. Notification payload contract (canonical field list and event types): **\[TBC — link]**

Always treat a notification as a trigger, not as truth: the current build does not verify the authenticity of inbound webhook calls, so re-query the payment state through the API before updating an order. Serve the endpoint behind TLS. Both points are covered in [Security mechanics](/integration/api-client-sdk/guides/security-mechanics.md#webhook-notifications).

## Complete example

A minimal end-to-end flow — create the client, expose a webhook endpoint, initiate a payment, and capture it once checkout completes:

```ts
import { DefaultEgwClientFactory, CitPaymentChannel } from "@tieto/egw-sdk";
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";

const client = new DefaultEgwClientFactory().create({
  baseUri: new URL("https://sandbox.api.ecommerce.sebgroup.com"),
  authentication: {
    tokenEndpoint: new URL("https://api.ecommerce.sebgroup.com/auth/realms/participants-seb/protocol/openid-connect/token"),
    clientId: process.env.EGW_CLIENT_ID!,
    clientSecret: process.env.EGW_CLIENT_SECRET!,
  },
  jwkUri: new URL("https://checkout.ecommerce.sebgroup.com/api/keys/card-encryption.jwk"),
});

const payments = client.payments();
const notifications = client.notifications();

createServer((req, res) => {
  const chunks: Buffer[] = [];
  req.on("data", (c) => chunks.push(c));
  req.on("end", () => {
    res.writeHead(200).end();
    const n = notifications.parseWebhook<{ paymentId: string; paymentStatus: string }>(
      Buffer.concat(chunks)
    );
    console.log(`Payment ${n.paymentId} → ${n.paymentStatus}`);
  });
}).listen(8080);

try {
  const created = await payments.initiateCitPaymentWithBuilder(randomUUID(), (b) =>
    b.channel(CitPaymentChannel.Browser) // affected by the open enum defect
     .instructedAmountBuilder((a) => a.amount("100.00").currency("EUR"))
     .orderNumber("ORD-1001")
     .participantId(process.env.EGW_PARTICIPANT_ID!)
     .shopAgreementId(process.env.EGW_SHOP_AGREEMENT_ID!)
     .shopRedirectUrl("https://shop.example.com/return")
     .description("Fishing rod, 2.7 m")
  );
  console.log("Payment created:", created.paymentId);

  // ... the shopper completes checkout; you learn the outcome via the
  // webhook endpoint above or by polling getPaymentInformation().

  const captured = await payments.capturePaymentWithBuilder(
    randomUUID(),
    created.paymentId!,
    (b) => b.instructedAmountBuilder((a) => a.amount("100.00").currency("EUR"))
  );
  console.log("Captured.");
} catch (e) {
  console.error("Payment flow failed:", e instanceof Error ? e.message : e);
}
```

## Next steps

* [Error handling](/integration/api-client-sdk/guides/error-handling.md) — the exception hierarchy in full, and retry guidance.
* [Security mechanics](/integration/api-client-sdk/guides/security-mechanics.md) — card encryption, webhook security.
* [Node.js quickstart](/integration/api-client-sdk/quickstarts/node.js-typescript.md) — install, credentials, known limitations.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ecommerce.seb.ee/integration/api-client-sdk/using-the-sdk/node.js-typescript.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
