Package Exports
- @fortsignal/sdk
- @fortsignal/sdk/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@fortsignal/sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@fortsignal/sdk
TypeScript client for FortSignal — register passkeys, run intent-bound challenges, verify humans or agents. Same API as api.fortsignal.com.
FortSignal hashes your action fields (action, amount, recipient, …) and has the device or agent sign that hash. Change anything after approval → verification fails. Optional dashboard policies and agent delegations apply when you set them up.
challenge = SHA-256(nonce : action : amount : recipient : from : metadata)Install
npm install @fortsignal/sdkHumans need WebAuthn in the browser:
npm install @simplewebauthn/browserAgents sign with Ed25519 on the server only — no extra SDK beyond this package.
Get a key after signup: fortsignal.com/signup → fs_live_...
Humans
import { FortSignal } from '@fortsignal/sdk'
const client = new FortSignal({ apiKey: process.env.FORTSIGNAL_API_KEY! })Register once (server starts options → browser creates passkey → server completes):
const options = await client.register.start({
userId: 'user_123',
saveMode: 'passwords', // optional
})
import { startRegistration } from '@simplewebauthn/browser'
const registrationJSON = await startRegistration({ optionsJSON: options })
await client.register.complete(registrationJSON)Each sensitive action:
const options = await client.challenge.start({
userId: 'user_123',
action: 'transfer',
amount: 500,
recipient: 'bob@example.com',
from: 'alice@example.com',
metadata: { orderId: 'ord_123' },
})
import { startAuthentication } from '@simplewebauthn/browser'
const assertion = await startAuthentication({ optionsJSON: options })
const result = await client.challenge.verify(assertion)
if (result.decision === 'allow') {
// result.signalId — log as receipt
} else {
// result.reason — e.g. parameters_tampered, invalid_challenge, policy_*, …
}Optional: if you only get a signalId back from the client, confirm it server-side:
const stored = await client.signal.get(signalIdFromClient)Throws FortSignalError for HTTP failures (err.code, err.status). Missing or wrong-tenant signals → signal_not_found; bad UUID → invalid_signal_id.
Agents
Register the agent’s public key once (private key stays on your side):
await client.agent.register({
agentId: 'my-agent-01',
publicKey: agentPublicKeyBase64url,
})Approve policy + delegation in the dashboard (not available via API key — by design).
Each action:
const { challenge } = await client.agent.startChallenge({
agentId: 'my-agent-01',
action: 'transfer',
amount: 250,
recipient: 'acct_456',
})
const challengeBytes = Buffer.from(challenge, 'base64url')
const sigBytes = await crypto.subtle.sign('Ed25519', privateKey, challengeBytes)
const signature = Buffer.from(sigBytes).toString('base64url')
const result = await client.agent.verify({
agentId: 'my-agent-01',
challenge,
signature,
})Errors
decision: 'deny' is a normal response — check result.reason.
Auth, rate limits, and real HTTP failures throw FortSignalError:
import { FortSignalError } from '@fortsignal/sdk'
try {
await client.challenge.start({ ... })
} catch (err) {
if (err instanceof FortSignalError) {
console.error(err.code, err.status)
}
}API surface
| Namespace | Methods |
|---|---|
client.register |
start({ userId, saveMode? }), complete(registrationJSON) |
client.challenge |
start(...), verify(assertion) |
client.signal |
get(signalId) |
client.agent |
register(...), startChallenge({ ..., delegationId? }), verify(...) |
Dashboard-only (session auth, not API key): POST /agent/delegate, POST /agent/revoke, GET /agent/list.
Full detail: api.fortsignal.com/docs
Requirements
Node.js 18+
License
MIT