# Node

> Sign user tokens and verify webhooks in Node with @nexafon/node.

Source: https://docs.nexafone.so/v1/server/node/

`@nexafon/node` needs Node 20.19 or later and has no dependencies. Types are included.

:::note[Not published yet]
The Nexafon packages are not on npm, Packagist or PyPI yet, and `go get` finds the Go module only once the Nexafon
repository is public and tagged, so installing them by name fails for now. Until then,
[install them from a checkout of the repo](/v1/quickstart/#install-from-a-checkout). The install commands shown here
are the ones to use after release.
:::

```sh
npm install @nexafon/node
```

## Token endpoint

```ts title="server.ts"
// The SDK calls getUserToken whenever it needs a session; answer with a fresh token for the signed-in user.
if (req.method === 'POST' && req.url === '/nexafon-token') {
  const user = signedInUser(req)
  if (!user) {
    res.writeHead(401).end()
    return
  }
  if (!mayUseNexafon(user)) {
    res.writeHead(403).end()
    return
  }
  // Signed with NEXAFON_SECRET_KEY (sk_test_… or sk_live_…), which never leaves this server.
  const token = createUserToken({
    userId: user.id,
    name: user.name,
    role: 'agent',
    email: user.email,
    // Nexafon links a first sign-in to the member with this email only when it is verified: never send true for
    // an address your app has not confirmed, or whoever sets a member's email on your app acts as that member.
    emailVerified: user.emailVerified,
  })
  res.writeHead(200, { 'content-type': 'text/plain', 'cache-control': 'no-store' }).end(token)
  return
}
```

:::caution[Send `emailVerified` as true only for a confirmed address]
Nexafon links a user’s first sign-in to the workspace member with the token’s email only when it is verified. If your
app lets users set an email it has not confirmed, someone could register with a member’s address and act as that
member. Send `true` only when your app has confirmed the address, from your own user record, never as a constant.
:::

Options: `secretKey` (defaults to `NEXAFON_SECRET_KEY`), `expiresIn` in seconds (default 300, at most 600). An invalid
input throws a `NexafonTokenError` naming the problem.

## Webhook receiver

```ts title="server.ts"
if (req.method === 'POST' && req.url === '/hooks/nexafon') {
  try {
    // The raw body, exactly as received: parsing it first changes the bytes the signature covers.
    const event = verifyWebhook(
      req.headers,
      await rawBody(req),
      process.env.NEXAFON_WEBHOOK_SECRET ?? '',
    )
    // Deliveries can repeat: skip an event.id you have already handled.
    console.log(event.type, event.id)
    res.writeHead(204).end()
  } catch (err) {
    if (!(err instanceof WebhookVerificationError)) throw err
    res.writeHead(400).end()
  }
  return
}
```

`verifyWebhook(headers, rawBody, secret)` accepts Node’s `req.headers`, a Fetch `Headers` object or a plain record;
`secret` can be a list while you rotate. With Express, read the body with `express.raw({ type: 'application/json' })`
so it stays raw.

The whole file runs as is (`node server.ts`); it is the [quickstart](/v1/quickstart/) server. For partners, see the
[Partner API](/v1/partners/api/).
