> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pulseshiga.io/llms.txt
> Use this file to discover all available pages before exploring further.

# @pulsebyshiga/node

> Server-side SDK reference — client construction, collection sessions and orders, rate preview, webhook verification, error classes, and the shared wire types.

The server-side SDK. It holds your secret key, mints single-order sessions,
looks up order ground truth, previews rates, and verifies signed webhooks.
Methods return the value directly and throw typed errors — see
[Error handling](/backend/errors) for the catch pattern.

<Info>
  This surface reflects the `@pulsebyshiga/node` types — the working view of the API
  until the canonical Pulse OpenAPI contract is published. Wire shapes
  (snake\_case JSON, decimal-string amounts) are stable; a few fields are still
  being reconciled and are flagged where they appear.
</Info>

## Client construction

Construct the client once with an auth descriptor and optional settings.

```ts theme={null}
import Pulse, { useApiKey } from '@pulsebyshiga/node';

const pulse = new Pulse(useApiKey(process.env.PULSE_API_KEY!), {
  webhookSecret: process.env.PULSE_WEBHOOK_SECRET,
});
```

The constructor is `new Pulse(auth, options)`. `auth` is either a bare API-key
string (treated as `useApiKey`) or an auth descriptor from one of the two
helpers below.

### Auth helpers

<ParamField path="useApiKey(apiKey)" type="PulseAuth">
  Full engine access with the partner's root secret. **Server-side only** — the
  client throws `PulseAccessError` if an API key is constructed where `window`
  is defined.
</ParamField>

<ParamField path="useSession(sessionToken)" type="PulseAuth">
  Scope every call to one order's short-lived session token (`cs_*`).
  Browser-safe. The session token is a scoped-down delegate the API key mints,
  so a caller holds one or the other, never both.
</ParamField>

### Options

<ParamField path="webhookSecret" type="string">
  Default secret for `webhooks.verify()` / `verifyOnce()`. Can also be passed
  per call.
</ParamField>

<ParamField path="webhookEventStore" type="ProcessedEventStore">
  Backing store for `verifyOnce()` de-duplication. Defaults to an in-memory
  store (single process). Supply a shared store (Redis, a database table)
  implementing `has(eventId)` / `add(eventId)` for multi-instance deployments.
</ParamField>

<ParamField path="baseUrl" type="string">
  Override the API host (e.g. a local or sandbox test server). Otherwise the live
  engine host is used.
</ParamField>

<ParamField path="maxRetries" type="number" default="2">
  Retries on network errors, 429, and 5xx.
</ParamField>

<ParamField path="retryDelayMs" type="number" default="250">
  Base backoff delay, doubled per attempt.
</ParamField>

<ParamField path="fetchImpl" type="FetchLike">
  Custom `fetch` implementation.
</ParamField>

## collectionSessions.create

```ts theme={null}
const session = await pulse.collectionSessions.create(params, { idempotencyKey });
```

Creates a collection order plus the single-order session token the hosted flow
is opened with. See [Create a session](/backend/create-session) for the naira
and USD gate walkthrough.

### Parameters

The payload is a discriminated union on `gate`. Provide **exactly one** of
`target` (fiat side) or `source` (crypto side).

<ParamField path="gate" type="&#x22;naira&#x22; | &#x22;usd&#x22;" required>
  Which fiat gate the session settles through.
</ParamField>

<ParamField path="direction" type="&#x22;offramp&#x22; | &#x22;onramp&#x22;" default="offramp">
  Value direction. `offramp` = crypto in → fiat out; `onramp` = fiat in → crypto
  out.
</ParamField>

<ParamField path="target" type="TargetAmount">
  Fiat side — provide this OR `source`.

  <Expandable title="TargetAmount">
    <ParamField path="currency" type="&#x22;NGN&#x22; | &#x22;USD&#x22;" required />

    <ParamField path="amount" type="string" required>
      Decimal string, e.g. `"1000000.00"`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="source" type="SourceAmount">
  Crypto side (offramp) — the amount of `asset` to send; the fiat target is
  derived from it.

  <Expandable title="SourceAmount">
    <ParamField path="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" required />

    <ParamField path="amount" type="string" required />
  </Expandable>
</ParamField>

<ParamField path="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" required />

<ParamField path="network" type="Network" required>
  Lowercase network name (see [Types](#types)).
</ParamField>

<ParamField path="partner_ref" type="string">
  Your own reference for this order.
</ParamField>

<ParamField path="user_ref" type="string">
  Naira gate only. Your stable reference for the end user.
</ParamField>

<ParamField path="account" type="NairaAccount">
  Naira gate only. Either provisions a named virtual account on first use, or
  references an existing one.

  <Expandable title="NairaAccount">
    <ParamField path="provision" type="boolean" required>
      `true` to provision a new account, `false` to reference an existing one.
    </ParamField>

    <ParamField path="bvn" type="string">
      When `provision: true`. Sensitive — server-to-server only; never reaches
      the hosted flow.
    </ParamField>

    <ParamField path="nin" type="string">
      When `provision: true`. Sensitive — server-to-server only; never reaches
      the hosted flow.
    </ParamField>

    <ParamField path="virtual_account_ref" type="string">
      When `provision: false`.
    </ParamField>
  </Expandable>
</ParamField>

The USD gate carries no `user_ref` and no `account` block — funds land in your
omnibus rather than a per-user account.

### Options

<ParamField path="idempotencyKey" type="string">
  Client idempotency key; auto-generated when omitted. Key it off your own
  deposit/order id so a retried request mints the same session.
</ParamField>

### Response — CollectionSession

<ResponseField name="order_id" type="string" />

<ResponseField name="session_token" type="string">
  Short-lived single-order token (`cs_*`) for the hosted flow. Returned once —
  hand this to the frontend, never the secret key.
</ResponseField>

<ResponseField name="deposit_address" type="string">
  Unique per order — the attribution key.
</ResponseField>

<ResponseField name="qr_payload" type="string">
  Solana Pay URI (solana) or EIP-681 URI (EVM).
</ResponseField>

<ResponseField name="quote" type="SessionQuote">
  <Expandable title="SessionQuote">
    <ResponseField name="rate" type="string">Fiat per 1 asset unit, decimal string.</ResponseField>

    <ResponseField name="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" />

    <ResponseField name="asset_amount" type="string">Exact stablecoin amount the user must send.</ResponseField>

    <ResponseField name="locked_until" type="string" />
  </Expandable>
</ResponseField>

<ResponseField name="status" type="OrderStatus" />

<ResponseField name="created_at" type="string" />

## collectionOrders

```ts theme={null}
const order = await pulse.collectionOrders.retrieve(orderId);
const orders = await pulse.collectionOrders.list({ gate: 'naira', status: 'awaiting_payment' });
const refreshed = await pulse.collectionOrders.refreshQuote(orderId);
```

Ground truth for an order — a fallback when a webhook is missed, and the
partner-scoped listing for reconciliation. See [Orders](/backend/orders).

* **`retrieve(orderId)`** → `CollectionOrder`
* **`list(params?)`** → `Page<CollectionOrder>`
* **`refreshQuote(orderId)`** → `CollectionOrder` — re-lock the quote for an
  expired order on a new window; does not create a new order.

### list parameters

<ParamField path="gate" type="&#x22;naira&#x22; | &#x22;usd&#x22;" />

<ParamField path="status" type="OrderStatus" />

<ParamField path="user_ref" type="string" />

<ParamField path="created_after" type="string" />

<ParamField path="created_before" type="string" />

<ParamField path="limit" type="number" />

<ParamField path="cursor" type="string" />

### Response — CollectionOrder

<ResponseField name="id" type="string" />

<ResponseField name="gate" type="&#x22;naira&#x22; | &#x22;usd&#x22;" />

<ResponseField name="direction" type="&#x22;offramp&#x22; | &#x22;onramp&#x22;" />

<ResponseField name="status" type="OrderStatus" />

<ResponseField name="amount_status" type="AmountStatus" />

<ResponseField name="target" type="TargetAmount" />

<ResponseField name="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" />

<ResponseField name="network" type="Network" />

<ResponseField name="user_ref" type="string | null" />

<ResponseField name="partner_ref" type="string | null" />

<ResponseField name="deposit_address" type="string">
  Offramp: where the user sends crypto. Onramp: unused (see `pay_account`).
</ResponseField>

<ResponseField name="deposit_addresses" type="Record<string, string>">
  Optional. Offramp deposit address per chain family/network when the engine
  returns them, so switching chains reuses the same order. Falls back to
  `deposit_address`.
</ResponseField>

<ResponseField name="qr_payload" type="string" />

<ResponseField name="pay_account" type="PayAccount | null">
  Onramp: the virtual bank account to pay into. Null on offramp.

  <Expandable title="PayAccount">
    <ResponseField name="bank_name" type="string" />

    <ResponseField name="bank_code" type="string" />

    <ResponseField name="bank_logo_url" type="string">Optional; hidden if it fails to load.</ResponseField>

    <ResponseField name="account_number" type="string" />

    <ResponseField name="account_name" type="string" />

    <ResponseField name="amount" type="string">Fiat to transfer (equals `target.amount`), decimal string.</ResponseField>

    <ResponseField name="expires_at" type="string" />
  </Expandable>
</ResponseField>

<ResponseField name="quote" type="SessionQuote" />

<ResponseField name="deposit" type="Deposit | null">
  <Expandable title="Deposit">
    <ResponseField name="tx_hash" type="string" />

    <ResponseField name="amount" type="string">On-chain detected amount — the credited figure, never the quoted one.</ResponseField>

    <ResponseField name="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" />

    <ResponseField name="network" type="Network" />

    <ResponseField name="detected_at" type="string" />

    <ResponseField name="confirmed_at" type="string | null" />
  </Expandable>
</ResponseField>

<ResponseField name="wrong_chain_deposit" type="WrongChainDeposit | null">
  Provisional shape — funds detected on a network other than the order's.

  <Expandable title="WrongChainDeposit">
    <ResponseField name="tx_hash" type="string" />

    <ResponseField name="amount" type="string" />

    <ResponseField name="asset" type="&#x22;USDC&#x22; | &#x22;USDT&#x22;" />

    <ResponseField name="network" type="string">Network the funds actually arrived on (may be outside the supported set).</ResponseField>

    <ResponseField name="detected_at" type="string" />
  </Expandable>
</ResponseField>

<ResponseField name="disbursement" type="Disbursement | null">
  <Expandable title="Disbursement">
    <ResponseField name="settlement_ref" type="string" />

    <ResponseField name="currency" type="&#x22;NGN&#x22; | &#x22;USD&#x22;" />

    <ResponseField name="amount" type="string" />

    <ResponseField name="virtual_account_ref" type="string | null">Naira gate only; null for USD (partner omnibus).</ResponseField>

    <ResponseField name="completed_at" type="string" />
  </Expandable>
</ResponseField>

<ResponseField name="created_at" type="string" />

The `list` response wraps orders in a page:

<ResponseField name="data" type="CollectionOrder[]" />

<ResponseField name="next_cursor" type="string | null" />

## rates.preview

```ts theme={null}
const preview = await pulse.rates.preview({ from: 'USDC', to: 'NGN', amount: '100.00' });
```

Look up the live rate without placing an order. Wraps `POST /v1/quotes` and
reshapes it for display; the returned `quoteId` proceeds to an order at the same
locked price. See [Preview a rate](/backend/preview-rate).

### Parameters — RatePreviewParams

<ParamField path="from" type="string" required>Source currency/asset, e.g. `"USDC"`.</ParamField>
<ParamField path="to" type="string" required>Destination currency, e.g. `"NGN"`.</ParamField>
<ParamField path="amount" type="string" required>Decimal amount in the source currency, e.g. `"100.00"`.</ParamField>
<ParamField path="network" type="EngineNetwork">Uppercase engine network name.</ParamField>

### Response — RatePreview

<ResponseField name="rate" type="string">Units of `to` per one unit of `from`, decimal string.</ResponseField>
<ResponseField name="youSend" type="string">What the user parts with (equals the requested amount), decimal string.</ResponseField>
<ResponseField name="youReceive" type="string">What the user gets at this rate, decimal string.</ResponseField>
<ResponseField name="expiresAt" type="string">When the locked rate expires (ISO 8601).</ResponseField>
<ResponseField name="quoteId" type="string">Pass to an order to transact at exactly this locked rate.</ResponseField>

<Note>
  `rates.preview` reshapes the response to camelCase (`youSend`, `expiresAt`,
  `quoteId`) for display — the only camelCase surface in the SDK. Everything else
  is snake\_case on the wire.
</Note>

## webhooks

```ts theme={null}
const event = pulse.webhooks.verify(rawBody, signatureHeader);
const eventOrNull = await pulse.webhooks.verifyOnce(rawBody, signatureHeader);
```

Verify a signed `Pulse-Signature` delivery and get the typed `WebhookEvent`.
`payload` MUST be the raw request body — a re-serialized JSON object will not
match the signature. See [Webhooks](/backend/webhooks) and the
[webhook event catalog](/reference/webhook-events).

<ParamField path="verify(payload, signatureHeader, secret?, options?)" type="WebhookEvent">
  Verifies the signature and replay tolerance, returns the typed event. Throws
  `SignatureVerificationError` on any mismatch.
</ParamField>

<ParamField path="verifyOnce(payload, signatureHeader, secret?, options?)" type="Promise<WebhookEvent | null>">
  Verifies AND de-duplicates on `event.id`. First sight of an id returns the
  event; a redelivery returns **`null`** (skip it, respond 200). Signature
  failures still throw before the store is touched.
</ParamField>

* `payload`: `string | Buffer` — the raw body.
* `secret`: optional; falls back to the constructor's `webhookSecret`.
* `options` (`VerifyOptions`): `toleranceSeconds` (default `300`) and `now`
  (injectable Unix-seconds clock).

## Errors

Every SDK error extends `PulseError`, so `catch (e) { if (e instanceof PulseError) … }`
catches everything from the SDK.

<ResponseField name="PulseApiError" type="extends PulseError">
  The engine responded non-2xx. Fields: `status` (number), `code` (string),
  `message` (string), `requestId` (string | null).
</ResponseField>

<ResponseField name="PulseConnectionError" type="extends PulseError">
  Network failure / timeout after the built-in retries. Field: `message`.
</ResponseField>

<ResponseField name="PulseAccessError" type="extends PulseError">
  A credential was used where it isn't allowed — e.g. an API key constructed in
  a browser. Field: `message`.
</ResponseField>

<ResponseField name="SignatureVerificationError" type="extends PulseError">
  Webhook verification failed — bad signature, replay, or malformed payload.
  Field: `message`.
</ResponseField>

## Types

<ResponseField name="Network" type="string union">
  Lowercase, product surface (sessions/orders): `ethereum`, `base`, `polygon`,
  `arbitrum`, `optimism`, `celo`, `plasma`. `solana` and `ton` are recognised
  but **coming soon**.
</ResponseField>

<ResponseField name="EngineNetwork" type="string union">
  UPPERCASE, engine quote surface (`rates.preview`): `BASE`, `POLYGON`,
  `ARBITRUM`, `OPTIMISM`, `ETHEREUM`, `BSC`.
</ResponseField>

<ResponseField name="Gate" type="string union">`naira`, `usd`.</ResponseField>
<ResponseField name="Asset" type="string union">`USDC`, `USDT`.</ResponseField>
<ResponseField name="FiatCurrency" type="string union">`NGN`, `USD`.</ResponseField>
<ResponseField name="CollectionDirection" type="string union">`offramp`, `onramp`.</ResponseField>

<ResponseField name="OrderStatus" type="string union">
  `awaiting_payment`, `deposit_detected`, `deposit_confirmed`, `converting`,
  `converted_unsettled`, `completed`, `expired`, `failed`.
</ResponseField>

<ResponseField name="AmountStatus" type="string union">
  `pending`, `exact`, `underpaid`, `overpaid`.
</ResponseField>

<Warning>
  **Casing matters.** `Network` (product surface, lowercase) and `EngineNetwork`
  (engine quote surface, UPPERCASE) are distinct types with different members —
  BSC exists only on `EngineNetwork`; solana/ton/celo/plasma only on `Network`.
  Pass the one the method's signature asks for.
</Warning>
