Docs
Node client
The same package as the CLI. A typed client for the REST API and a webhook verifier, with no dependencies.
Install#
npm i shipnotedNode 18 or later (it uses the built-in fetch). ESM, with TypeScript types included. Use it on a server: an API key must never reach a browser.
The client#
import { createClient } from "shipnoted"
const sn = createClient({ key: process.env.SHIPNOTED_API_KEY, project: "your-slug" })
// releases
const { release } = await sn.releases.create({ title: "Dark mode", body: "- Dark mode", tags: ["new"], publish: true, email: true })
await sn.releases.update(release.id, { version: "v1.4.0" })
// requests
const { feedback } = await sn.feedback.list({ status: "open", sort: "revenue", segment: "beta:true" })
await sn.feedback.setStatus("SN-42", "planned", "Q1")
await sn.feedback.reply("SN-42", "On it", { internal: true })
const { ref } = await sn.feedback.create({ title: "Export to CSV", email: "ada@acme.com", idempotencyKey: "ticket-8841" })
// everything else
await sn.themes(); await sn.segments(); await sn.loop()
await sn.subscribers.list({ status: "active" })
await sn.webhooks.create({ url: "https://example.com/hook" })Every method returns the parsed JSON. The shape of each is in the API reference, and createClient also accepts url (a different host) and fetch (your own, for tests or proxies).
Errors#
import { ShipnotedError } from "shipnoted"
try {
await sn.releases.publish("r_abc")
} catch (e) {
if (e instanceof ShipnotedError) console.log(e.status, e.message, e.body) // 409 "That release is already published."
}Verify webhooks#
import { verifyWebhook, WebhookVerificationError } from "shipnoted"
const event = verifyWebhook({
secret: process.env.SHIPNOTED_WEBHOOK_SECRET, // whsec_…
headers: req.headers, // a Headers object or a plain object
body: rawBody, // the raw string or Buffer, not parsed JSON
})
// → { id, type, created, data } or throws WebhookVerificationErrorIt checks the signature in constant time, accepts any one of several signatures (so you can rotate secrets), and refuses events more than five minutes old (toleranceSec changes that). Full guide: Webhooks.
Testing your handler#
import { signWebhook } from "shipnoted"
const body = JSON.stringify({ id: "evt_1", type: "feedback.created", created: 1, data: { title: "Hi" } })
const headers = signWebhook({ secret, id: "wd_1", body })
const res = await app.request("/hooks/shipnoted", { method: "POST", headers, body })