POS API SDK
Reference for @gobtcpay/pos-api-sdk, the official client for the GoBTC Pay API. Use it to accept Bitcoin payments from a point-of-sale terminal: create a payment, show the customer the QR code, and track it until it settles on-chain. The same package also ships GoBTCPayServer, a server-side client for shops (see Server client).
This page covers version 2.0.0.
For a setup walkthrough, see Accept Payments Online.
Where to put the apiKey. The apiKey is a per-terminal secret used to sign every request. Keep it server-side, or on a controlled POS device. Do not ship it in a public website. For browser usage, the API must also allow your origin via CORS, otherwise route the calls through your own backend.
Overview
The package is a thin client for the POS API surface. It signs each request for you with a fresh HMAC signature and timestamp, so there is no token to manage or refresh.
The same package runs on a server, in a bundled browser app, and in a plain HTML page, because it uses the Web Crypto API (built into both browsers and Node) rather than node:crypto.
Use it when you are building a checkout or POS integration against a specific terminal, and you hold that terminal's key.
Installation
npm install @gobtcpay/pos-api-sdkRuns on Node 18 or newer and in modern browsers. Both ESM and CommonJS entry points ship, along with TypeScript declarations.
import { GoBTCPay } from "@gobtcpay/pos-api-sdk";const { GoBTCPay } = require("@gobtcpay/pos-api-sdk");Initialization
Construct the client once. The terminal key signs every request automatically.
import { GoBTCPay } from "@gobtcpay/pos-api-sdk";
const btcPay = new GoBTCPay({
apiKey: process.env.POS_API_KEY,
posTerminalId: process.env.POS_TERMINAL_ID,
});| Option | Required | Default | Description |
|---|---|---|---|
apiKey | yes | none | Per-terminal secret used to sign every request. |
posTerminalId | no | none | Default terminal for createPayment. |
baseUrl | no | https://api.gobtcpay.com/public/api/v1.1 | Override for staging or dev environments. |
timeoutMs | no | 30000 | Per-request timeout. |
fetch | no | global fetch | Custom fetch implementation. |
The POS client never retries a failed request: a failure is thrown on the first attempt. Retries exist only in GoBTCPayServer.
API version is pinned in the SDK. The version (v1.1, exported as API_VERSION) lives inside the package, so you do not put it in a URL. The SDK always speaks the API version it was built and tested against. To target a different environment, override baseUrl.
Reference
Classes and types
| Name | Kind | Description |
|---|---|---|
GoBTCPay | class | The POS client. Construct it with the options above. |
GoBTCPayServer | class | The server client for shops, authenticated with a secret key. See Server client. |
PaymentPoller | class | Returned by watchPayment(). Holds live state, subscriptions, and a done() promise. |
WebhookHandler | class | Returned by webhooks(). Verifies, de-duplicates and dispatches webhook deliveries. |
FINAL_STATUSES | constant | Statuses at which the poller stops by default. See Auto-polling. |
MIN_POLL_INTERVAL_MS | constant | Minimum and default polling interval: 3000. |
API_VERSION | constant | The API version the POS client speaks (v1.1). |
SERVER_API_VERSION | constant | The API version GoBTCPayServer speaks (v1.2). |
SIGNATURE_HEADER | constant | Name of the webhook signature header, lower-cased: "x-gobtcpay-signature". |
isTestDelivery | function | isTestDelivery(delivery) is true for a test delivery and narrows the type: when it is false, delivery.data is the payment. See Webhooks. |
WebhookSearchLimitError | class | Thrown by webhookEndpoints.findByUrl when its page limit is reached. See Errors. |
WebhookDelivery | type | What a webhook listener receives: a WebhookEvent or a WebhookTestEvent. |
WebhookTestEvent | type | A test delivery: { eventId, type, createdAt, test: true, data: null }. |
WebhookEndpoint | type | A registered webhook endpoint: { id, merchantId, url, scope, events, status, label, createdAt, updatedAt }. Carries no signing secret. |
WebhookEndpointWithSecret | type | WebhookEndpoint plus signingSecret. Returned only by webhookEndpoints.create. |
WebhookEndpointStatus | type | "active" or "disabled". |
WebhookScope | type | Which deliveries an endpoint receives: { type: "merchant" }, { type: "store", storeId } or { type: "pos", posTerminalId }. Not a restriction on the API key. |
CreateWebhookEndpointParams | type | Parameters of webhookEndpoints.create: { url, scope?, events?, label? }. |
ListWebhookEndpointsParams | type | Parameters of webhookEndpoints.list: { status?, limit?, skip? }. |
A payment's status field is a string. This page does not describe what each status means; see Payment Lifecycle.
Methods
| Method | Signature | Description |
|---|---|---|
createPayment | createPayment({ amount, currency, description?, ttlSeconds?, externalId?, posTerminalId? }) | Create a payment. Returns paymentId and qrString to show the customer. |
getPayment | getPayment({ paymentId }) | Fetch the current state of a payment. |
watchPayment | watchPayment({ paymentId, intervalMs?, timeoutMs?, until?, immediate?, stopOnError? }) | Poll getPayment until one of the until statuses. Returns a started PaymentPoller. |
webhooks | webhooks({ signingSecret, toleranceSeconds?, dedupeCacheSize? }) | Create a webhook handler (see Webhooks). |
PaymentPoller members:
| Member | Description |
|---|---|
state | Live, readable object: { status, payment, error, attempts, isPolling, isFinal, startedAt, updatedAt }. |
start() | Starts polling and returns the poller. watchPayment() already calls it. A no-op while running or once stopped at an until status. |
onChange(cb) | Fires on any state change. Returns an unsubscribe function. |
onUpdate(cb) | Fires on every successful poll. |
onPaid(cb) | Fires when a poll returns paid and the previously known status was not paid. |
onSettled(cb) | Fires when a poll returns one of the until statuses. |
onError(cb) | Fires when a poll fails. |
done() | Promise that resolves with the payment that stopped the poller, or rejects on timeout (or on the first error with stopOnError). Call it before the poller stops: a done() first called after the poller has reached an until status never settles. |
stop() | Stops polling early. Does not resolve done(). |
Events
The only named event is the webhook event below. The PaymentPoller delivers its updates through the subscription methods in the table above (onChange, onUpdate, onPaid, onSettled, onError), not as named events.
| Event | Payload | Description |
|---|---|---|
payment.status.updated | { eventId, type, createdAt, data }; data has the getPayment() shape. A test delivery has the same type with test: true and data: null | A payment changed status, or the platform sent a test delivery. Delivered to your webhook endpoint (see Webhooks). |
Errors
Every error class the SDK defines extends GoBTCPayError. Each error's name is its class name.
| Error | Extends | When it is thrown | Extra fields |
|---|---|---|---|
GoBTCPayError | Error | Base class. Thrown directly for invalid client setup: missing apiKey, no fetch, a rejected key in GoBTCPayServer, missing posTerminalId in createPayment. Also thrown by webhookEndpoints.create when the response does not contain the created endpoint, and by webhookEndpoints.test when the platform does not confirm it queued the test delivery. | none |
GoBTCPayApiError | GoBTCPayError | The API answered with an error. Thrown as-is only when the HTTP status is below 400; otherwise one of the subclasses below. | httpStatus, body, requestId, getter type (body.data.type) |
AuthError | GoBTCPayApiError | HTTP 401 or 403. | same as GoBTCPayApiError |
NotFoundError | GoBTCPayApiError | HTTP 404. | same as GoBTCPayApiError |
RateLimitError | GoBTCPayApiError | HTTP 429. | retryAfterMs, parsed from the Retry-After header when the API sends it |
ValidationError | GoBTCPayApiError | Any other 4xx. | same as GoBTCPayApiError |
ServerError | GoBTCPayApiError | HTTP 500 or above. | same as GoBTCPayApiError |
NetworkError | GoBTCPayError | No response: connection failure, a timeout, or an aborted AbortSignal. | isTimeout, cause |
WebhookSignatureError | GoBTCPayError | A webhook delivery failed verification, or its body was already parsed. | none |
WebhookSearchLimitError | GoBTCPayError | webhookEndpoints.findByUrl scanned 20 full pages without finding the URL; whether the URL is registered is unknown. | pagesScanned, scannedCount, totalCount (as reported by the server; diagnostic only) |
requestId is the X-Request-Id response header; quote it in support requests.
One rejection is not a GoBTCPayError: when a poller's timeoutMs runs out, done() rejects with a plain Error (Payment poll timed out after <timeoutMs>ms).
Auto-polling
watchPayment() polls getPayment on an interval and returns a started PaymentPoller with live state, subscriptions, and a done() promise. Polling stops when a poll returns one of the until statuses.
| Option | Default | Behavior |
|---|---|---|
until | FINAL_STATUSES | Stops at paid, expired or canceled. The exported list also contains cleared and failed. Passing until replaces the list. |
timeoutMs | 0 | No timeout: the poller runs until an until status, or until you call stop(). |
intervalMs | 3000 | Delay between polls. Values below 3000 are raised to 3000. |
immediate | true | Poll once right away instead of waiting one interval. |
stopOnError | false | Keep polling after a failed request. With true, the first error stops polling and rejects done(). |
const watcher = btcPay.watchPayment({ paymentId, intervalMs: 3000 });
// Live state, readable at any time:
watcher.state; // { status, payment, error, attempts, isPolling, isFinal, ... }
// Subscriptions (each returns an unsubscribe function):
watcher.onChange((state) => render(state.status)); // any state change
watcher.onUpdate((payment) => {}); // every successful poll
watcher.onPaid((payment) => {}); // a poll returned `paid`
watcher.onSettled((payment) => {}); // a poll returned an `until` status
watcher.onError((err) => {}); // a poll failed
// Or just await the outcome:
const finalPayment = await watcher.done();
watcher.stop(); // stop earlyBy default the poller stops at expired, so it misses a late payment. The package itself notes that expired is not final on the server: funds that arrive after expiry can still settle the payment. A poller that has stopped at expired will not see it.
The primary channel for payment updates is the webhook. If you poll, pass until without expired and set your own timeoutMs, so polling covers the time you keep the order open and still ends.
import { FINAL_STATUSES } from "@gobtcpay/pos-api-sdk";
const watcher = btcPay.watchPayment({
paymentId,
until: FINAL_STATUSES.filter((s) => s !== "expired"),
timeoutMs: 60 * 60 * 1000, // how long you keep the order open
});Webhooks
Register a webhook URL in the merchant dashboard, or from code with webhookEndpoints.create on the server client, to receive payment.status.updated events. Prefer webhooks to polling. The handler verifies the X-GoBTCPay-Signature header, enforces a replay window, and de-duplicates on eventId.
import { isTestDelivery } from "@gobtcpay/pos-api-sdk";
const webhooks = btcPay.webhooks({ signingSecret: process.env.POS_WEBHOOK_SECRET });
webhooks.on("payment.status.updated", (event) => {
// A test delivery has the same type but no payment: event.data is null.
if (isTestDelivery(event)) return;
// event.data has the same shape as getPayment()
console.log(event.eventId, event.data.paymentId, event.data.status);
});A listener receives a WebhookDelivery: either a payment delivery or a test delivery (test: true, data: null) that the platform sends with the same type, for example after webhookEndpoints.test. A test delivery is dispatched to the listeners of its type like any other. In TypeScript with strictNullChecks (on under strict), reading data as a payment without first narrowing with isTestDelivery(event) does not compile. Test deliveries go through the same eventId de-duplication as payment deliveries.
| Option | Default | Behavior |
|---|---|---|
signingSecret | none | Required. The secret the signature is computed with. |
toleranceSeconds | 300 | Rejects a delivery whose signed timestamp differs from the current time by more than this, in either direction. 0 disables the check. |
dedupeCacheSize | 1000 | How many recent eventIds to remember. Beyond that, the oldest is forgotten. 0 disables de-duplication. |
The header has the form t=<unix seconds>,v1=<hex>, where v1 is an HMAC-SHA256 of <t>.<raw body> with the signing secret.
De-duplication lives in process memory. The seen eventIds are kept inside one WebhookHandler object. A second instance of your service, or the same service after a restart, does not know them. If you run more than one instance, or must not process an event twice across restarts, de-duplicate by eventId yourself in durable storage.
The handler records an eventId before it calls your listeners. If a listener throws, a redelivery of that event to the same handler is treated as a duplicate and your listeners are not called again. Make listeners that must not lose an event persist it first, or set dedupeCacheSize: 0 and de-duplicate yourself.
Framework adapters
Pass the raw request body, not a re-serialized object, or the signature will not match. The adapters read it for you:
declare const SIGNATURE_HEADER = "x-gobtcpay-signature";
declare const expressWebhookHandler: (handler: WebhookHandler) => (req: NodeLikeRequest, res: NodeLikeResponse) => Promise<unknown>;
declare const fastifyWebhookHandler: (handler: WebhookHandler) => (request: NodeLikeRequest, reply: {
code: (code: number) => {
send: (body?: unknown) => unknown;
};
}) => Promise<unknown>;
declare const fetchWebhookHandler: (handler: WebhookHandler) => (request: Request) => Promise<Response>;
declare const verifyRequest: (handler: WebhookHandler, rawBody: string, signatureHeader: string | null | undefined) => Promise<WebhookDelivery | null>;- All three handlers reply
200once a delivery is verified and dispatched, and also for a duplicate. They reply400with the error message on aWebhookSignatureError. Any other error, including one thrown by your listener, is rethrown to your framework. expressWebhookHandlerandfastifyWebhookHandlerreadreq.rawBody, falling back toreq.body. It must be a string or bytes (Buffer/Uint8Array); an already parsed body is rejected with400.fetchWebhookHandlerreads the body withrequest.text(), so it needs no parser setup. It suits any runtime withRequest/Response, such as Next.js route handlers.verifyRequestverifies, de-duplicates and dispatches likehandle(), and returns the delivery (which may be a test delivery), ornullfor a duplicate. Use it for frameworks the adapters do not cover.
import { expressWebhookHandler, fetchWebhookHandler } from "@gobtcpay/pos-api-sdk";
// Express
app.post("/webhooks/gobtcpay", express.raw({ type: "application/json" }), expressWebhookHandler(webhooks));
// Next.js: app/api/gobtcpay/webhook/route.ts
export const POST = fetchWebhookHandler(webhooks);Without an adapter, call the handler directly:
handle(rawBody, signatureHeader)returns the parsed delivery (WebhookDelivery), ornullif it was a duplicate.constructEvent(rawBody, signatureHeader)verifies and parses without de-duplicating or dispatching, if you want to handle routing yourself. It also returns aWebhookDelivery; narrow it withisTestDeliverybefore readingdata.
Server client (GoBTCPayServer)
GoBTCPayServer is the client for a shop's backend: create a payment for an order, track it, register webhook endpoints, and verify webhooks. It speaks API v1.2 (exported as SERVER_API_VERSION) and sends the merchant secret key as a Bearer token in the Authorization header.
import { GoBTCPayServer } from "@gobtcpay/pos-api-sdk";
const gobtcpay = new GoBTCPayServer({ apiKey: process.env.GOBTCPAY_SECRET_KEY });
const payment = await gobtcpay.payments.create({
amount: 49.99,
currency: "USD",
externalId: `order-${order.id}`,
});
redirect(payment.checkoutUrl);Only a secret key is accepted. The constructor throws a GoBTCPayError for a key that starts with pk_live_ (a publishable key), and for any key that does not start with sk_live_. Keep the secret key server-side.
| Option | Required | Default | Description |
|---|---|---|---|
apiKey | yes | none | Merchant secret key, sk_live_…. |
baseUrl | no | https://api.gobtcpay.com/public/api/v1.2 | Override for staging or dev environments. |
timeoutMs | no | 30000 | Per-request timeout. |
maxRetries | no | 2 | Retries after the first attempt. 0 disables retries. |
fetch | no | global fetch | Custom fetch implementation. |
onEvent | no | none | Called once per request attempt, for logging and metrics. An exception it throws is ignored. |
| Method | Signature | Description |
|---|---|---|
payments.create | create({ amount, currency, externalId?, description?, ttlSeconds? }, options?) | Create a payment. Retried only when externalId is set (see Retries). |
payments.get | get({ paymentId }, options?) | Fetch the current state of a payment. |
payments.cancel | cancel({ paymentId }, options?) | Cancel a payment. |
payments.list | list({ status?, externalId?, dateRange?, limit? }, options?) | Async iterator over payments; fetches pages of limit rows (default 50, at most 100) as you iterate. |
payments.listPage | listPage({ status?, externalId?, dateRange?, limit?, skip? }, options?) | One page: { items, totalCount }, for driving paging yourself. |
watchPayment | watchPayment({ paymentId, ... }) | The same PaymentPoller as above, polling payments.get. See Auto-polling. |
webhooks | webhooks({ signingSecret, toleranceSeconds?, dedupeCacheSize? }) | The same WebhookHandler as above. See Webhooks. |
webhookEndpoints.create | create({ url, scope?, events?, label? }, options?) | Register a webhook endpoint. Returns a WebhookEndpointWithSecret. Never retried automatically. See Managing webhook endpoints. |
webhookEndpoints.list | list({ status?, limit?, skip? }, options?) | One page of endpoints: { items, totalCount }. limit is clamped to 1–100, default 50. Elements carry no signing secret. |
webhookEndpoints.test | test({ webhookId }, options?) | Ask the platform to send a test delivery to the endpoint. Resolves with no value. Never retried automatically. |
webhookEndpoints.findByUrl | findByUrl(url, options?) | Find the endpoint registered on exactly this URL. Returns a WebhookEndpoint or null. |
for await (const p of gobtcpay.payments.list({ externalId: `order-${order.id}` })) {
await reconcile(p);
}Every payments.* and webhookEndpoints.* method takes an optional last argument options: { signal?: AbortSignal; retry?: boolean }. signal aborts the request and any pending retry.
Retries
GoBTCPayServer retries a request after a NetworkError (including a timeout) or an HTTP 429 or 5xx response, up to maxRetries times. Before a retry it waits for the Retry-After time of a 429 when the API sends one, otherwise a random delay between 0 and 250 ms × 2^n (n = 0 before the first retry), capped at 4 seconds. Other errors are thrown at once.
payments.get, payments.cancel, payments.list, payments.listPage, webhookEndpoints.list and the page requests of webhookEndpoints.findByUrl are retried. webhookEndpoints.create and webhookEndpoints.test are not. payments.create is retried only when you pass externalId; without it, a failed create is thrown on the first attempt and is not repeated. According to the package's own documentation, externalId makes create idempotent on the API side, so a retry returns the payment already created for that order instead of a second one. The retry request option overrides the SDK's choice for any payments.* or webhookEndpoints.* call; with maxRetries: 0 nothing is retried.
Managing webhook endpoints
webhookEndpoints registers your webhook endpoints from code. It is separate from webhooks(), which checks the deliveries that reach your server. webhookEndpoints exists only on GoBTCPayServer, not on the POS client, and its requests go to the v1.2 API with the secret key.
import { isTestDelivery } from "@gobtcpay/pos-api-sdk";
const url = "https://shop.example.com/webhooks/gobtcpay";
// Throws WebhookSearchLimitError when the answer is unknown: do not create then.
const existing = await gobtcpay.webhookEndpoints.findByUrl(url);
if (existing === null) {
const endpoint = await gobtcpay.webhookEndpoints.create({ url });
// The only time the secret is returned. Store it now.
await saveSigningSecret(shop.id, endpoint.signingSecret);
}
const webhooks = gobtcpay.webhooks({ signingSecret: await loadSigningSecret(shop.id) });
webhooks.on("payment.status.updated", async (delivery) => {
if (isTestDelivery(delivery)) return; // acknowledged; there is no payment in it
await markOrderPaid(delivery.data.paymentId);
});- Call
findByUrlfirst and callcreateonly when it returnsnull.createis never retried and does not check for an existing endpoint on the same URL. IffindByUrlthrowsWebhookSearchLimitError, do not create blindly: the endpoint may already exist. scopeis optional and defaults to{ type: "merchant" }, all deliveries of the merchant. It decides which deliveries the endpoint receives; it is not a restriction on your API key.signingSecretis returned only bycreate.listandfindByUrldo not return it, and it cannot be read again. A lost secret can only be replaced by rotating it, which invalidates the previous one. The SDK has no method to rotate the secret, or to update or delete an endpoint.- A URL the platform refuses throws
ValidationError, anderr.typeis thewebhook_url_*code, for examplewebhook_url_private_host. createis never retried automatically: the platform has no idempotency key for it. After aNetworkErroror a timeout the outcome is unknown. If the response does not contain the endpoint,createthrows aGoBTCPayError, and the endpoint may still have been created. In both cases look the URL up withfindByUrlbefore creating again.testresolving means only that the platform queued a test delivery. Whether the endpoint works is known only when that delivery reaches your handler withtest: true. If the platform does not confirm the queueing,testthrows aGoBTCPayError.findByUrlscans the listing itself: 100 endpoints per page, at most 20 pages (2000 endpoints), comparing URLs by exact string equality. It returns the endpoint if found, andnullif the whole listing was scanned without a match. It throwsWebhookSearchLimitErrorwhen the 20th page is still full: the answer is then unknown, and you should not create an endpoint on the strength of it. The scan stops on a page shorter than 100, not on the server'stotalCount.
A store-scoped key is refused. An API key limited to one store gets HTTP 403 (AuthError, err.type === "permission_error") on create, list and test, and therefore on findByUrl, whatever scope you pass. Nothing in the error tells this refusal apart from a revoked or unknown key except the message text, which is not part of the contract. Treat any AuthError here as "this key cannot do this" and use a merchant-level secret key.
The SDK does not prevent duplicate endpoints. "Find, then create" is two requests with nothing atomic between them. Two processes connecting the same shop at the same time can both find nothing and both create, leaving two endpoints on one URL. Serialize connecting yourself, for example with a lock held for the duration of the connect.
Examples
Server (Node.js / Bun / Deno)
import { GoBTCPay, FINAL_STATUSES } from "@gobtcpay/pos-api-sdk";
const btcPay = new GoBTCPay({
apiKey: process.env.POS_API_KEY,
posTerminalId: process.env.POS_TERMINAL_ID,
});
// Create a payment and show the QR to the customer.
const payment = await btcPay.createPayment({
amount: 10,
currency: "USD",
description: "Order #1024",
});
console.log(payment.paymentId, payment.qrString);
// Wait for the outcome, without stopping at `expired`.
const final = await btcPay
.watchPayment({
paymentId: payment.paymentId,
until: FINAL_STATUSES.filter((s) => s !== "expired"),
timeoutMs: 60 * 60 * 1000,
})
.done();
console.log(final.status);A common pattern is to keep the secret on your server and expose thin endpoints (POST /payments, GET /payments/:id) that the browser calls.
Browser (with a bundler: Vite, webpack, Next.js)
import { GoBTCPay } from "@gobtcpay/pos-api-sdk";
const btcPay = new GoBTCPay({ apiKey, posTerminalId });
const payment = await btcPay.createPayment({ amount: 10, currency: "USD" });
// Watch for the result and update the UI live.
const watcher = btcPay.watchPayment({ paymentId: payment.paymentId });
watcher.onChange((s) => (statusEl.textContent = s.status));
watcher.onPaid(() => showPaidScreen());Plain HTML (no build step)
Load the ESM build from a CDN inside <script type="module">. The @2 in the URL keeps the page on the 2.x line of the package. Use this only on a test or controlled device, because the key is a secret.
<!doctype html>
<html>
<body>
<pre id="out">creating...</pre>
<script type="module">
import { GoBTCPay } from "https://esm.sh/@gobtcpay/pos-api-sdk@2";
const btcPay = new GoBTCPay({
apiKey: "YOUR_TERMINAL_KEY", // secret: test or controlled device only
posTerminalId: "YOUR_TERMINAL_ID",
});
const out = document.getElementById("out");
try {
const payment = await btcPay.createPayment({ amount: 1, currency: "USD" });
out.textContent = `paymentId: ${payment.paymentId}\nqr: ${payment.qrString}`;
const watcher = btcPay.watchPayment({ paymentId: payment.paymentId });
watcher.onChange((s) => (out.textContent += `\nstatus: ${s.status}`));
} catch (err) {
out.textContent = "error: " + err.message;
}
</script>
</body>
</html>The browser must be allowed to reach the API origin (CORS). If you hit a Failed to fetch or CORS error, proxy the requests through your backend and point baseUrl at that proxy.
Versioning
Each client pins the API version it speaks inside the package:
| Client | Constant | API version |
|---|---|---|
GoBTCPay | API_VERSION | v1.1 |
GoBTCPayServer | SERVER_API_VERSION | v1.2 |
The package version tracks the API version via semver:
| API version | SDK version |
|---|---|
v1.x | 0.x / 1.x / 2.x |
- Non-breaking API changes (
v1.1 → v1.2) ship as a minor SDK release. - A breaking API version (
v2) ships as a new major SDK release that points at the new base URL internally. - While the SDK itself is
0.x, a breaking change to the contract it declares also ships as a minor release, per the semver convention for0.x—0.4.0changed every satoshi field fromnumbertostring. Read the changelog before bumping a minor.
2.0.0 is a major release because of a breaking change in the SDK's own types, not because of a new API version: webhook listeners now receive, and handle(), constructEvent() and verifyRequest() now return, a WebhookDelivery (which may be a test delivery) instead of a WebhookEvent. Both clients still speak the API versions in the table above.