POST /api/v1/invoices
invoices:write — Create an invoice; Idempotency-Key required.
DEVELOPER DOCUMENTATION / BITCOIN
Create invoices, track payment status, and receive signed callbacks. Customer payments go to the merchant’s wallet—not a BlockAck-held balance.
Not an engineer? Explore the payment-link and website-button guide. Hosted payment and donation links are available for a ready, verified Bitcoin store. Use them only for lawful sales.
Bitcoin payments require a ready store, verified receiving addresses, and a completed settlement check. Creating a store alone does not make it ready to take payments. Hosted links use that same check. Examples do not create payments.
Base URL: https://www.blockack.com. Send your scoped merchant API key as Authorization: Bearer YOUR_API_KEY. Never include API keys in public links, website buttons, browser JavaScript, or source control.
Create an account, connect your Bitcoin extended public key, configure your store, and create a scoped API key in the merchant console. You do not connect a separate payment server. Account-management endpoints use a merchant session token instead of this API key.
This server-side JavaScript example requests 25,000 sats. Use your local BlockAck store UUID and an API key with invoices:write. Check the effective USD-equivalent payment limit before submitting.
// Server-side only. Never expose your API key in browser code.
const response = await fetch("https://www.blockack.com/api/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLOCKACK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order-1042-invoice",
},
body: JSON.stringify({
amount_sats: 25000,
store_id: process.env.BLOCKACK_STORE_ID,
email: "customer@example.com",
metadata: { order_id: "1042" },
}),
});
const invoice = await response.json();
if (!response.ok) throw new Error(invoice.error ?? "Invoice creation failed");
// Save invoice.id and checkout_url against your order on your server.
// Confirm settlement before fulfilling; a successful POST is not payment.Send either amount_sats as a positive integer, or {"amount":"0.00025000","currency":"BTC"}. Do not combine these forms. Fractional satoshis are rejected rather than rounded. Fiat numeric amounts are also supported; currency defaults to USD when omitted. email is required. BlockAck sends the receipt to that address after the payment settles. Optional description says what the customer is paying for. The same field works on every invoice currency and on both payment and donation links.
Keep one Idempotency-Key (8–128 characters) per intended invoice. Reuse it with the same payload on retry. A completed replay returns Idempotency-Replayed: true. If creation is in progress or unresolved, investigate the original request; do not switch to a new key and risk a duplicate invoice.
Create the store, register the extended public key, and attach it. Read the store again until it is ready. A private key or a seed phrase is refused.
const key = process.env.BLOCKACK_API_KEY;
const headers = { Authorization: `Bearer ${key}`, "Content-Type": "application/json" };
const store = await fetch("https://www.blockack.com/v2/stores", {
method: "POST",
headers,
body: JSON.stringify({
name: "Harbor Goods",
callback_url: "https://example.com/hooks/harbor",
environment: "production",
}),
});
const { id: storeId } = await store.json();
const wallet = await fetch("https://www.blockack.com/v2/wallets", {
method: "POST",
headers,
body: JSON.stringify({ crypto: "BTC", name: "Shop wallet", xpub: "xpub..." }),
});
const { id: walletId } = await wallet.json();
await fetch(`https://www.blockack.com/v2/stores/${storeId}/wallets`, {
method: "POST",
headers,
body: JSON.stringify({ wallet_id: walletId }),
});
// Read the store again until it is ready. A private key or seed phrase is refused.Create the shop, then add products from a spreadsheet. A shop can hold 40 products. The spreadsheet also needs invoices:write.
const shop = await fetch("https://www.blockack.com/v2/shops", {
method: "POST",
headers,
body: JSON.stringify({
store_id: storeId,
name: "Harbor Goods",
slug: "harbor-goods",
summary: "Wool and paper.",
category: "clothing",
terms_accepted: true,
seller_accepted: true,
}),
});
const { shop: created } = await shop.json();
await fetch(`https://www.blockack.com/v2/shops/${created.id}/products`, {
method: "POST",
headers,
body: JSON.stringify({
csv: "Title,amount_sats,Option1 Name,Option1 Value,Variant Inventory Qty,Type,description\nNotebook,15000,Size,A5,12,payment,<p>Plain paper.</p>\nCard,6000,Finish,Matte,20,payment,A folded note.",
}),
});Change the description, option, note for the buyer, or the digital delivery text. Send one or more of those fields. The product stays off the public shop until the review finishes.
await fetch(`https://www.blockack.com/v2/shops/${shopId}/products/${linkId}`, {
method: "POST",
headers,
body: JSON.stringify({
description_html: "<p>Ruled paper, 48 pages.</p>",
option_label: "Size: A5",
buyer_prompt: "Name to print",
fulfillment: "payment",
}),
});Send the file itself. Content-Type is image/jpeg, image/png, or image/webp. The file is at most 1 MB, and no side is longer than 1600 pixels. A product with no photo has no public image. The banner and the logo use the same rules.
const file = await readFile("notebook.png");
await fetch(`https://www.blockack.com/v2/shops/${shopId}/products/${linkId}/image`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.BLOCKACK_API_KEY}`, "Content-Type": "image/png" },
body: file,
});
// Banner: POST /v2/shops/{id}/cover
// Logo: POST /v2/shops/{id}/logo
// Same file rules. The body is the file itself.The cart request does not use your API key. The code in the response opens the pay page.
const quote = await fetch("https://www.blockack.com/api/stores/harbor-goods/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
request_id: crypto.randomUUID(),
lines: [{ link_id: linkId, quantity: 1 }],
shipping_id: shippingId,
}),
});
const { code } = await quote.json();
// Open /pay?code= with that code. This request does not use your API key.These routes require merchant API authentication and the permission shown. List responses include pagination; cryptocurrency atomic amounts in ledger responses are strings.
/api/v1/invoicesinvoices:write — Create an invoice; Idempotency-Key required.
/v2/invoicesinvoices:read — List invoices; filter by store_id, status, dates, limit and offset.
/v2/invoices/{id}invoices:read — Read an invoice by its id.
/v2/invoices/{id}/receiptinvoices:write — Create or rotate an expiring shareable receipt after recorded BTC settlement.
/v2/invoices/{id}/receiptinvoices:write — Revoke the current receipt link for your invoice and key environment.
/v2/paymentspayments:read — Read your recorded payment ledger, not arbitrary blockchain history.
/v2/storesstores:read — List your stores and copy the store id.
/v2/webhookswebhooks:read — Inspect merchant callback delivery records.
/v2/webhooks/{id}/retrywebhooks:write — Requeue a failed callback delivery.
A shop is a catalog on a store you already created. Creating and publishing use the permission shown. The public directory does not use an API key.
/v2/shopsstores:read — List the shops on your account.
/v2/shopsstores:write — Create a shop on one of your stores.
/v2/shops/{id}stores:read — Read one shop, including its products.
/v2/shops/{id}stores:write — Publish or unpublish a shop, change its category, or accept the current seller terms.
/v2/shops/{id}/productsstores:write — Add existing payment links, import a spreadsheet, or set the product order.
/v2/shops/{id}/products/{linkId}stores:write — Set a product option, buyer prompt, description, or digital delivery text.
/v2/shops/{id}/designstores:write — Set the shop colors, layout, banner, and button label.
/v2/shops/{id}/products/{linkId}/imagestores:write — Upload, read, or remove a product image.
/v2/shops/{id}/products/{linkId}stores:write — Remove a product from the shop. The payment link stays.
/v2/shops/{id}/coverstores:write — Upload, read, or remove the shop banner.
/v2/shops/{id}/logostores:write — Upload, read, or remove the shop logo.
/v2/shops/directorypublic — List published shops. Search by name or description and filter by category.
/v2/shops/directory/{slug}public — Read one published shop and its products.
The shop name becomes the public address: Harbor Goods is /stores/harbor-goods. Publish waits until that store's extended public key is verified and the current seller terms are accepted. An account can have three shops, and a shop can have 40 products. A spreadsheet import also needs invoices:write because it creates payment links. A product image is JPEG, PNG, or WebP, up to 1 MB, with no side longer than 1600 pixels. Pay for the cart with POST /api/stores/{slug}/checkout. That request does not use your API key. The response code opens /pay?code=. The banner and the logo use the same image rules at /v2/shops/{id}/cover and /v2/shops/{id}/logo. DELETE /v2/shops/{id}/products/{linkId} takes a product off the shop. The payment link stays.
Receipt POST accepts an empty JSON object or {"expires_in_seconds":1209600} (maximum 30 days). It returns reference, expires_at, and a bearer url whose page offers copy/share/download QR controls. Repeating POST invalidates the previous link; DELETE returns 204. An ineligible or inaccessible invoice returns 404.
Anyone with the link can view the original invoice amount, currency and recorded settlement time. No customer details or transaction identifiers are disclosed. This is stored settlement evidence, not fresh chain verification or a tax invoice.
Configure a public HTTPS callback URL on your store. Local/private addresses, embedded credentials, and custom ports are rejected. Delivery is asynchronous and may be retried; do not assume events arrive once or in order.
Verify the BlockAck-Sig header: sha256= followed by the hexadecimal HMAC-SHA256 of the unmodified request body, using the callback signing secret. Compare signatures in constant time. This is not your API key. Securely arrange the signing secret with the platform operator; there is no self-service callback-secret retrieval endpoint documented here.
The envelope has id, type, and data. Handle invoice.settled, deduplicate by delivery id, and check the invoice against your server-side order record. Return a 2xx response after durable acceptance. A redirect, invoice-created response, or browser success screen is not proof of settlement.
Each merchant shares 1,000 free authorized API requests per UTC month across keys and stores. Starter is 10,000 sats/month or 96,000 sats/year. Paid access does not bypass short-window rate limits or payment-creation safety limits. The default API rate limit is 120 requests per key per 60 seconds.
Current launch defaults: $50 USD-equivalent per creation and 1,000 creations per merchant per UTC month, including paid accounts. Check your effective limits in Billing. Polling, authorized failures, and idempotent retries count toward free API usage.
Inspect the response’s error code. Use bounded backoff for transient failures, and preserve the original idempotency key. Never automatically mark an order paid when an API request fails.