> ## 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.

# Quickstart

> Zero to a working sandbox integration: create a session, render the payment moment, and credit off the webhook.

<Note>
  **What's live today — updated 2026-07-23.** The direct engine APIs — quotes, rates,
  offramp, onramp, and bank resolution, called from your backend with an `sk_` API key —
  are live and reconciled against the Pulse engine. The **embedded collection product**
  documented here (server-minted `cs_` client sessions, the drop-in components, and signed
  webhooks) is in **preview**: it runs against the sandbox today, and order lifecycle is
  observed by polling [`collectionOrders.retrieve`](/backend/orders) rather than by webhook.
  Treat the client-session and webhook shapes as provisional until they land on the engine —
  details in [Contract status](/reference/contract-status).
</Note>

<Steps>
  <Step title="Install the backend SDK">
    `@pulsebyshiga/node` is the server component of Pulse. It runs only on your backend,
    holds your API key, and talks to Pulse over HTTPS.

    <CodeGroup>
      ```bash npm theme={null}
      npm install @pulsebyshiga/node
      ```

      ```bash pnpm theme={null}
      pnpm add @pulsebyshiga/node
      ```

      ```bash yarn theme={null}
      yarn add @pulsebyshiga/node
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a collection session">
    Construct the client once with your API key, then mint a session. The `usd` gate is
    the simplest path — no `user_ref`, no `account` block; funds land in your omnibus.

    ```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,
    });

    // USD gate — no user_ref, no account block; funds land in your omnibus.
    const session = await pulse.collectionSessions.create(
      {
        gate: 'usd',
        target: { currency: 'USD', amount: '100.00' },
        asset: 'USDT',
        network: 'base',
      },
      { idempotencyKey: `fund-${depositId}` },
    );
    ```

    The `naira` gate carries per-user identity instead: add `user_ref` and an `account`
    block — `{ provision: true, bvn, nin }` for a first-time customer (Pulse provisions
    the virtual account), or `{ provision: false, virtual_account_ref }` for a returning
    one.
  </Step>

  <Step title="Hand the session token to your frontend">
    Return only `session.session_token` to the client. Never expose your `sk_` key.

    ```ts theme={null}
    // Return only the token to your frontend — never the sk_ key.
    return { session_token: session.session_token };
    ```
  </Step>

  <Step title="Render the payment moment">
    Mount the embedded component with the session token. `onSuccess` is a UX signal
    only — credit the customer from the webhook, not from this callback.

    ```tsx theme={null}
    import { PulseCollectPayment } from '@pulsebyshiga/collect-react';

    <PulseCollectPayment sessionToken={sessionToken} onSuccess={onFunded} />;
    ```

    This is the hosted, embedded model. See
    [Render the payment moment](/render/choose-a-model) for the in-app, hosted-page,
    and headless alternatives.
  </Step>

  <Step title="Verify the webhook">
    Delivery is at-least-once and signed (`Pulse-Signature`, HMAC-SHA256 over the
    **raw** body). `verifyOnce` verifies the signature, checks replay tolerance, and
    de-duplicates on event id — it returns `null` for a redelivery.

    <Note>
      In preview, the sandbox delivers these webhooks. Against the live engine today the
      webhook surface isn't live yet, so you observe lifecycle by polling
      [`collectionOrders.retrieve`](/backend/orders) — the handler below is how it will
      work once webhooks land.
    </Note>

    ```ts theme={null}
    // Express — note express.raw: the raw body is required for the signature.
    app.post('/webhooks/pulse', express.raw({ type: '*/*' }), async (req, res) => {
      const event = await pulse.webhooks.verifyOnce(req.body, req.header('pulse-signature') ?? '');
      if (event === null) return res.sendStatus(200); // duplicate — already processed

      switch (event.type) {
        case 'disbursement.completed':
          await creditPosition(event.data.order_id, event.data.disbursement.settlement_ref);
          break;
        case 'collection.amount.underpaid':
          await handleShortfall(event.data); // { expected, received, asset, network, tx_hash }
          break;
        case 'collection.deposit.wrong_chain':
          // funds arrived on the wrong network; the order is still payable on the right one
          await flagForSupport(event.data); // { order_id, deposit, expected_network }
          break;
      }
      res.sendStatus(200);
    });
    ```

    <Warning>
      **Credit a customer only after a verified `disbursement.completed` webhook.**
      Client-side callbacks such as `onSuccess` are UX signals — they can be spoofed.
      The signed webhook is the only source of truth for crediting a user.
    </Warning>
  </Step>

  <Step title="Drive it in the sandbox">
    Point the SDK at your sandbox key (`sk_test_*`). Simulate the full lifecycle
    without funds — each call fires the real signed webhooks:

    ```sh theme={null}
    POST /sandbox/collection-orders/{order_id}/simulate
    { "event": "deposit_detected" }        # optional "received_amount" to force a mismatch
    { "event": "deposit_confirmed" }
    { "event": "disburse" }
    ```

    A full happy path is: create session → `deposit_detected` → `deposit_confirmed` →
    `disburse` → receive `disbursement.completed` → credit.
  </Step>
</Steps>
