Skip to content

Start here

Someone has given you a project key and a webhook secret. This is everything you need to send your first message, and nothing you don’t.

If you have not been given a key

Ask whoever runs the deployment. Keys are minted with the root key, which lives with operators and never in a project — see Quickstart for that side.

1. Three environment variables#

These are the only three a consuming project needs.

env
SWITCHBOARD_URL=https://switchboard.atrix.dev
SWITCHBOARD_KEY=<your project key>
SWITCHBOARD_WEBHOOK_SECRET=<your webhook secret>

Both secrets are shown exactly once

The project key and the webhook secret are displayed when they are created and are never readable again. If you have lost one, it has to be re-minted — the webhook secret via POST /v1/projects/{id}/webhook/rotate, which also tells the operator which instances must reconnect.

2. Install the SDK#

It is not on npm yet. The tarball lives in the Switchboard repository at sdk/releases/ — copy the current one into your project and depend on the path.

shell
git clone https://github.com/atrixdigital/atrix-switchboard /tmp/switchboard

mkdir -p vendor
cp /tmp/switchboard/sdk/releases/atrixdigital-switchboard-0.4.0.tgz vendor/

npm install
package.json
"dependencies": {
  "@atrixdigital/switchboard": "file:vendor/atrixdigital-switchboard-0.4.0.tgz"
}

Do not point at a sibling checkout

file:../../atrix-switchboard/sdk works on a laptop with both repos present and does not exist inside a Docker build or on a CI runner — which is where installs actually happen. Commit the tarball.

3. Send a message#

typescript
import { Switchboard } from "@atrixdigital/switchboard"

const wa = new Switchboard({
  url: process.env.SWITCHBOARD_URL!,
  projectKey: process.env.SWITCHBOARD_KEY!,
})

const { id } = await wa.outbound.send({
  to: "+971500000001",
  message: { text: "hello" },
})

const { state } = await wa.outbound.status(id)
// queued → sent → delivered → read

202, not 200

send() resolves when the message is accepted, not sent. Switchboard picks one of your numbers when the message is actually due, paces it, and retries it. Ask status(id) for what happened.

4. Receive messages#

typescript
// app/api/webhooks/switchboard/[secret]/route.ts
import { createWebhookHandler } from "@atrixdigital/switchboard"

export const POST = createWebhookHandler({
  secret: process.env.SWITCHBOARD_WEBHOOK_SECRET!,
  seen: (id) => db.deliveries.exists(id),
  onEvent: async ({ event, meta }) => {
    if (event.kind !== "inbound") return
    await db.deliveries.insert(meta.deliveryId)
    await queue.push(event)        // do the WORK off the request
  },
})

The secret is the last path segment, not a header — the engine’s webhook producer cannot attach headers. The handler verifies it, parses, dedupes and acknowledges for you.

Delivery is at-least-once

The same event arrives again whenever a delivery succeeded and the acknowledgement was lost. Dedupe on meta.deliveryId — a unique index on it is the whole implementation. Without it, a handler that sends an auto-reply sends it twice.

5. Check it worked#

shell
# no key needed — is the service up at all
curl https://switchboard.atrix.dev/v1/ready

# your project's own view
curl -s https://switchboard.atrix.dev/v1/usage    -H "apikey: $SWITCHBOARD_KEY"
curl -s https://switchboard.atrix.dev/v1/messages -H "apikey: $SWITCHBOARD_KEY"

# why is nothing going out?
curl -s https://switchboard.atrix.dev/v1/pool     -H "apikey: $SWITCHBOARD_KEY"
If you getIt means
401Bad key — or your project has been suspended. The two answer identically on purpose.
404 on someone else's idCorrect. Foreign resources are never 403, so ids cannot be enumerated.
429Your rate limit, or a number's daily cap. retryAfter is set for the first.
503A number is moving between replicas. The one error worth retrying.

What to read next#

Sending for the pool and pacing, Receiving for the four things every integration gets wrong, and Numbers for why a number can accept every message and deliver none.