The Musqet developer platform
Musqet has a REST API so your own software can do what the dashboard does: read your transactions and settlements, create and manage payment links, run a terminal sale, and more. It can also call you back: register a webhook and Musqet will POST to your server the moment a payment is created or completed.
There are two sides to this, and you will use both.
- In My Musqet you create the API keys your code authenticates with, and you register the webhook endpoints Musqet delivers to. This is point-and-click, and it is where the secrets live.
- From your own code you send requests with your key and receive webhooks on your endpoint. That is the rest of this page.
The API in one place
Section titled “The API in one place”Every endpoint lives under the /api/v1 prefix. Rather than list them all here, where a copy would drift from the code, Musqet publishes the catalogue live:
- The OpenAPI document is the machine-readable OpenAPI 3 description of every endpoint, its inputs and its responses, for generating a client, importing into Postman or Insomnia, or reading from an agent.
- The interactive reference is the same document rendered for a person, with examples, and shows the base URL to use.
The examples below use two shell variables so you can paste them as-is once you have set them:
export MUSQET_API_URL="…" # the base URL shown at the top of /docsexport MUSQET_API_KEY="msqt_api_live_…" # created in My Musqet, see belowAPI keys
Section titled “API keys”An API key is the credential your code presents on every request. Musqet stores only a hash of the key, never the key itself, so a key is shown to you once at the moment you create it and can never be retrieved again. It carries a set of permissions, so a key can be scoped to only what a given integration needs.
Create a key
Section titled “Create a key”In My Musqet, open API keys and create one. You give it:
- a name, so you can tell your keys apart later,
- a preset, which fills in the permissions a known integration needs so you do not have to know them yourself. Pick your integration (for example Odoo) and its permissions are selected for you. Pick Custom to choose the permissions yourself.
- the permissions it should hold, chosen from your business’s permission set. See Permissions below for what each one allows.
- optionally an expiry date, after which the key stops working on its own.
The new key is shown in full one time, prefixed msqt_api_live_. Copy it into your secret store there and then. If you lose it, you cannot see it again: revoke it and make a new one.
Permissions
Section titled “Permissions”A key carries a set of permissions, and every endpoint requires one. If a key does not hold the permission an endpoint needs, the request comes back 403. Grant a key only the permissions its integration actually uses.
Each permission has a name you see in My Musqet, a code you pass in the API’s permissions array (and see quoted in a 403), and the actions it allows.
| In My Musqet | Permission | What it lets a key do |
|---|---|---|
| View sales | sales:read | Read your sales and settlement history. |
| View Bitcoin backend | bitcoin-backend:read | Read your Bitcoin node and wallet configuration. |
| Manage Bitcoin backend | bitcoin-backend:write | Change your Bitcoin node and wallet configuration. |
| View payments | take-payments:read | Read your payments and hosted checkouts. |
| Take payments | take-payments:write | Create hosted checkouts and take payments. |
| View tip links | tip-links:read | Read your tip links. |
| Manage tip links | tip-links:write | Create and delete tip links. |
| Issue refunds | refunds:write | Issue refunds against sales. |
| Withdraw funds | withdraw:write | Move funds out of your Musqet balance. |
| View backup | backup:read | Read your wallet backup data. |
| View devices | devices:read | Read your enrolled terminals and their settings. |
| Manage devices | devices:write | Enrol, configure and remove terminals. |
| View team | team:read | Read your team members and their roles. |
| Manage team | team:write | Invite, edit and remove team members. |
| View API keys | api-keys:read | List the API keys on your business. |
| Manage API keys | api-keys:write | Create and revoke API keys. |
| View webhooks | webhooks:read | List your webhook endpoints and their delivery history. |
| Manage webhooks | webhooks:write | Create, edit and remove webhook endpoints, and rotate their signing secrets. |
| Customise dashboard | dashboard:write | Change your dashboard layout. |
| Verify UCP Lightning preimages | ucp:verify | Verify Lightning payment preimages. |
| Manage notification recipients | notifications:write | Manage who receives your notifications. |
| View terminal sales | terminal-sales:read | Read sales taken on a payment terminal. |
| Take terminal sales | terminal-sales:write | Start, poll and cancel sales on a payment terminal. |
The Odoo preset selects View devices (devices:read), View terminal sales (terminal-sales:read) and Take terminal sales (terminal-sales:write): what the point-of-sale integration needs to resolve a terminal by serial and run a sale.
Revoke a key
Section titled “Revoke a key”If a key leaks, or an integration is retired, revoke it from the same API keys screen. Revocation takes effect immediately: the very next request that presents it is rejected. Revoking one key does not touch your others.
Authenticating a request
Section titled “Authenticating a request”Present your key as a bearer token in the Authorization header:
curl "$MUSQET_API_URL/api/v1/transactions" \ -H "Authorization: Bearer $MUSQET_API_KEY"Musqet hashes the key you send, looks it up, and checks that it is not revoked and not past its expiry. If it fails any of those, the request comes back 401. If the key is valid but does not hold the permission an endpoint requires, the request comes back 403. The key also decides which business the request acts on, so you never pass a business id yourself.
Two things are worth knowing:
- The key is a static bearer token, not a signature. Send it only over HTTPS, and keep it server-side. There is no request-signing step on the calling side.
- The checkout endpoints are the exception.
POST /api/v1/checkoutsand reading or subscribing to a checkout need no API key, because they are meant to be driven from a customer’s browser where a secret key must never appear. Everything else under/api/v1needs a key. See Payment links and hosted checkout for that flow.
API errors
Section titled “API errors”Every error comes back as JSON in one shape:
{ "error": { "code": 401, "message": "Invalid or inactive API key.", "metadata": {} }}code is the HTTP status, repeated in the body so it survives logging. message is a human-readable reason. metadata is present only on some errors and carries structured detail.
| Status | Meaning | What to do |
|---|---|---|
400 | The request body or query failed validation | Fix the input; the message names the field |
401 | Missing, malformed, revoked or expired key | Check the Authorization header and the key’s state |
403 | The key is valid but lacks the required permission | Grant the permission to the key, or use one that has it |
404 | The thing you asked for does not exist | Check the id in the path |
429 | You are being rate limited | Back off and retry after a short wait |
500 | Something failed on our side | Retry; if it persists, contact support |
Webhooks
Section titled “Webhooks”A webhook lets Musqet call you. You register a URL, and Musqet POSTs a small JSON body to it when an event you asked for happens, so you do not have to poll. This is the outbound direction and is entirely separate from the callbacks Musqet receives from card and bitcoin processors.
Add an endpoint
Section titled “Add an endpoint”In My Musqet, open Webhooks and add one. You give it:
- the URL to POST to (it must be a public HTTPS address in production; private and internal addresses are refused),
- the one event it should receive, and
- an optional description.
An endpoint listens for a single event type. To receive more than one event, add an endpoint per event, pointing at the same URL. When you create an endpoint, Musqet shows you its signing secret once, prefixed whsec_. Store it: you need it to verify deliveries, and like an API key it cannot be retrieved again.
The events you can subscribe to
Section titled “The events you can subscribe to”There are four:
| Event | Fires when |
|---|---|
payment.created | A payment has been started for one of your checkouts |
payment.completed | That payment has completed |
terminal.sale.completed | A sale on a payment terminal completed |
terminal.sale.failed | A sale on a payment terminal failed |
Every delivery has the same body:
{ "eventType": "payment.completed", "checkoutId": "…", "externalId": "your-own-reference-or-null", "status": "…", "amount": 0, "currency": "GBP", "network": null, "txIds": [], "createdAt": "2026-08-12T10:00:00.000Z"}externalId is the reference you set when you created the checkout, so you can tie the event back to your own order. network and txIds are populated for bitcoin payments and are null or empty otherwise.
Verify a delivery came from Musqet
Section titled “Verify a delivery came from Musqet”Anyone can POST to a public URL, so verify the signature before you trust a delivery. Each request carries these headers:
| Header | Holds |
|---|---|
x-webhook-signature | t=<timestamp>,v1=<hmac> |
X-Webhook-Timestamp | The Unix timestamp used in the signature |
X-Webhook-Event | The event type |
X-Webhook-Delivery | The delivery id |
The signature is an HMAC-SHA256, keyed with your endpoint’s signing secret, over the string <timestamp>.<raw-body>. Recompute it from the raw request body (not a re-serialised copy of the parsed JSON) and compare in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto'
const verify = (rawBody, header, secret) => { const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
// Reject a delivery whose timestamp is too old to be genuine, so a captured // request cannot be replayed later. Widen or narrow the window to suit you. if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
const expected = createHmac('sha256', secret) .update(`${parts.t}.${rawBody}`, 'utf8') .digest('hex')
const a = Buffer.from(expected) const b = Buffer.from(parts.v1 ?? '') return a.length === b.length && timingSafeEqual(a, b)}Reject anything that does not match. The freshness check rejects a delivery whose timestamp is more than five minutes old, so a request captured off the wire cannot be replayed against you later.
Rotate a signing secret
Section titled “Rotate a signing secret”If a secret leaks, rotate it from the endpoint’s page in My Musqet. You get a fresh whsec_ secret, shown once. Update your verification code to the new value.
See what was delivered
Section titled “See what was delivered”Each endpoint keeps a delivery history in My Musqet: what was sent, the response status your server returned, and whether it succeeded or failed. Use it when an event does not arrive.
Musqet retries a failed delivery for you. A delivery counts as successful only on a 2xx response. A non-2xx, a timeout (deliveries time out after 30 seconds) or a network error is retried with an increasing delay, for up to five attempts in total (the first delivery and four retries). If an endpoint fails ten deliveries in a row, Musqet disables it and stops sending, so a dead URL does not pile up forever. Re-enable it once your server is healthy again.
Kiosk API
Section titled “Kiosk API”The kiosk API is a separate, smaller surface, under /kiosk/v1, for driving a Musqet payment terminal from your own kiosk or till software: fetch the business and terminal details for a serial, start a sale, then poll or cancel it.
It authenticates differently from the main API. Instead of a bearer key, a kiosk request carries an x-api-key header, and the value is the kiosk key issued for that installation rather than one of the per-business API keys above. The endpoints are listed in the live reference alongside the rest.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
Every request returns 401 | Key is wrong, revoked or expired, or the header is malformed | Confirm the header is Authorization: Bearer msqt_api_live_… and the key is active in My Musqet |
A request returns 403 | The key lacks the permission that endpoint needs | Grant the permission to the key, or use one that has it |
| A webhook never arrives | The endpoint is for a different event, or a previous run of failures disabled it | Check the event type on the endpoint and its delivery history in My Musqet |
| The signature check fails | Verifying against a re-serialised body, or against the wrong secret | Sign the raw request body, and confirm you are using this endpoint’s current whsec_ secret |
| A webhook stopped after a bad deploy | Ten consecutive failures disabled the endpoint | Fix your server, then re-enable the endpoint |
See also
Section titled “See also”- Payment links and hosted checkout — the public checkout endpoints, which need no API key
- Taking card payments
- Taking bitcoin payments