Skip to content

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

Terminal window
npm install @nexafon/node
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
}

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

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 server. For partners, see the Partner API.