> For the complete documentation index, see [llms.txt](https://docs.selcier.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.selcier.xyz/build-on-selcier/first-integration.md).

# Your first integration

A sandbox walkthrough of the Selcier API that covers a transfer check, a test share class deployment and one verified webhook delivery.

Work through this page with an empty project and you will finish with a running integration against the Selcier sandbox. That environment sits on Robinhood Chain testnet (chain ID `46630`). Its transactions are genuine on-chain transactions, yet no real shares or real money are involved.

Along the way you will have:

* called the API using a sandbox secret key
* added the TypeScript SDK to the project
* asked whether a share class's rules would allow a transfer
* launched a share class for testing on testnet
* accepted a webhook and confirmed its signature

## What you need first

A Selcier account with sandbox access is the only prerequisite. Sandbox secret keys are generated in the [Console](https://app.selcier.xyz/console), under **Developers → API keys**. You can recognize one by its `sk_test_` prefix.

{% hint style="warning" %}
A secret key is enough to move company configuration, invite holders and create offerings. Treat it as server-only: it does not belong in a browser, a mobile app or source control.
{% endhint %}

Export it to your shell as an environment variable:

```bash
export SELCIER_SECRET_KEY="sk_test_51Hx...redacted"
```

{% stepper %}
{% step %}

### Add the client library

You will need Node.js 18 or later for this package.

```bash
npm install @selcier/sdk
```

Next, instantiate a client. Routing depends on the key prefix, which means a `sk_test_` key always reaches `https://api.sandbox.selcier.xyz/v1`.

{% code title="selcier.ts" %}

```ts
import { Selcier } from "@selcier/sdk";

export const selcier = new Selcier(process.env.SELCIER_SECRET_KEY!);
```

{% endcode %}
{% endstep %}

{% step %}

### Locate the demo company

Sandbox accounts are provisioned with a demo company. It is already set up with a share class, a handful of verified holders and an open Liquidity Window. Listing your companies gives you its id.

{% tabs %}
{% tab title="curl" %}

```bash
curl https://api.sandbox.selcier.xyz/v1/companies \
  -H "Authorization: Bearer $SELCIER_SECRET_KEY" \
  -H "Selcier-Version: 2026-10-01"
```

{% endtab %}

{% tab title="TypeScript" %}

```ts
const companies = await selcier.companies.list({ limit: 10 });
const demo = companies.data[0];
console.log(demo.id, demo.name); // cmp_8f2kQ1xZ "Larkway Robotics (Sandbox)"
```

{% endtab %}
{% endtabs %}

What comes back uses the usual list shape:

```json
{
  "data": [
    {
      "id": "cmp_8f2kQ1xZ",
      "object": "company",
      "name": "Larkway Robotics (Sandbox)",
      "network": "robinhood_chain_testnet",
      "chain_id": 46630,
      "company_id_onchain": "0x6e6f727468...",
      "share_classes": ["cls_3VbT9pLm"],
      "created_at": "2026-10-01T14:02:11Z"
    }
  ],
  "has_more": false
}
```

{% endstep %}

{% step %}

### Run a transfer check

Get a verdict before you build any transaction that moves a Selcier Share Token. The evaluation applies the rules the token itself enforces on-chain, so `ok: true` tells you the transfer will go through as long as nothing changes before it lands.

{% tabs %}
{% tab title="curl" %}

```bash
curl https://api.sandbox.selcier.xyz/v1/transfers/check \
  -H "Authorization: Bearer $SELCIER_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d1c2f0e-quickstart-check" \
  -d '{
    "share_class": "cls_3VbT9pLm",
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x2222222222222222222222222222222222222222",
    "amount": "250"
  }'
```

{% endtab %}

{% tab title="TypeScript" %}

```ts
const check = await selcier.transfers.check({
  share_class: "cls_3VbT9pLm",
  from: "0x1111111111111111111111111111111111111111",
  to: "0x2222222222222222222222222222222222222222",
  amount: "250",
});

if (!check.ok) {
  console.log(check.reason_code, check.message);
}
```

{% endtab %}
{% endtabs %}

This example comes back as a failure because no identity is registered for the recipient:

```json
{
  "object": "transfer_check",
  "ok": false,
  "reason_code": "RECIPIENT_NOT_VERIFIED",
  "message": "The receiving wallet has no verified identity for this company.",
  "evaluated_at_block": 18204417
}
```

For the full set of values `reason_code` can take, see [Transfer outcome codes](/build-on-selcier/transfer-outcomes.md).
{% endstep %}

{% step %}

### Add a share class of your own

Give the demo company one more share class. This call goes through the factory, which deploys a `SelcierShareToken` and its `RuleEngine`. Expect `status: "deploying"` in the response, followed by `deployed` when inclusion of the transaction has happened.

{% tabs %}
{% tab title="curl" %}

```bash
curl https://api.sandbox.selcier.xyz/v1/share_classes \
  -H "Authorization: Bearer $SELCIER_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4a9e-quickstart-series-a" \
  -d '{
    "company": "cmp_8f2kQ1xZ",
    "name": "Series A Preferred",
    "symbol": "NWR-A",
    "modules": {
      "region": { "blocked": ["KP", "IR"] },
      "holding_period": { "days": 365 },
      "holder_cap": { "max_holders": 500 }
    }
  }'
```

{% endtab %}

{% tab title="TypeScript" %}

```ts
const seriesA = await selcier.shareClasses.create(
  {
    company: "cmp_8f2kQ1xZ",
    name: "Series A Preferred",
    symbol: "NWR-A",
    modules: {
      region: { blocked: ["KP", "IR"] },
      holding_period: { days: 365 },
      holder_cap: { max_holders: 500 },
    },
  },
  { idempotencyKey: "4a9e-quickstart-series-a" },
);

console.log(seriesA.id, seriesA.status); // cls_9QwE2rTy "deploying"
```

{% endtab %}
{% endtabs %}

A token address appears on the share class when deployment ends. Look it up on the testnet block explorer or the Selcier Explorer.
{% endstep %}

{% step %}

### Accept a signed delivery

With an endpoint registered, news of deployments, transfers and offerings reaches you in real time. During local development, put your server behind any tunnel you like and register the tunnel's public URL.

```ts
const endpoint = await selcier.webhookEndpoints.create({
  url: "https://example.ngrok.app/selcier/webhooks",
  events: ["share_class.deployed", "transfer.settled", "transfer.blocked"],
});

console.log(endpoint.secret); // whsec_... shown once, store it securely
```

Confirm that each delivery is genuine before acting on it:

{% code title="server.ts" %}

```ts
import express from "express";
import { selcier } from "./selcier";

const app = express();

app.post("/selcier/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = selcier.webhooks.constructEvent(
      req.body,
      req.header("Selcier-Signature")!,
      process.env.SELCIER_WEBHOOK_SECRET!,
    );
  } catch {
    return res.status(400).send("Invalid signature");
  }

  if (event.type === "share_class.deployed") {
    console.log("Deployed at", event.data.object.token_address);
  }
  res.sendStatus(200);
});

app.listen(3000);
```

{% endcode %}

Within a few seconds, `share_class.deployed` should arrive for the share class from the previous step. [Event delivery](/build-on-selcier/event-delivery.md) covers delivery details, retries and the complete list of events.
{% endstep %}
{% endstepper %}

## Where to go from here

* Before switching to live keys, read [Keys and environments](/build-on-selcier/keys-and-environments.md).
* For the complete endpoint listing, open the [REST API](/build-on-selcier/rest-api.md) page.
* Building a wallet, venue or data product? Skip ahead to [Supporting Selcier tokens](/build-on-selcier/supporting-selcier-tokens.md).


---

# 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.selcier.xyz/build-on-selcier/first-integration.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.
