REST API

The BlockPay REST API.

All requests are JSON over HTTPS. Amounts are integer-string minor units (USDC has 6 decimals, so 4900000 is $4.90). Times are Unix seconds.

POST/api/invoices

Create an invoice

Creates a fresh open invoice. Returns the canonical id, the on-chain commitment id and a checkout URL you can redirect the customer to.

Request

POST /api/invoices
Content-Type: application/json
{
"merchantId": "merchant_acme",
"merchantAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": "4900000",
"currency": "USDC",
"chainKey": "arc-testnet",
"lineItems": [
{ "label": "Pro Plan", "amount": "4900000" }
],
"expiresAt": 1747353600
}

Response

HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "inv_01HE2K6BX9C0",
"onChainInvoiceId": "0x9b1c...e3f0",
"checkoutUrl": "/checkout/inv_01HE2K6BX9C0",
"invoice": {
"id": "inv_01HE2K6BX9C0",
"merchantId": "merchant_acme",
"merchantAddress": "0xA0b8...eB48",
"amount": "4900000",
"currency": "USDC",
"chainKey": "arc-testnet",
"status": "open",
"lineItems": [{ "label": "Pro Plan", "amount": "4900000" }],
"createdAt": 1747350000,
"expiresAt": 1747353600
}
}

Error codes

CodeMeaning
400 invalid_jsonBody could not be parsed as JSON.
400 invalid_fieldA required field was missing or malformed. The response includes the failing field name.
GET/api/invoices/{id}

Fetch a single invoice

Returns the latest known state of one invoice, including settlement information once the on-chain transfer has confirmed.

Request

GET /api/invoices/inv_01HE2K6BX9C0

Response

HTTP/1.1 200 OK
Content-Type: application/json
{
"invoice": {
"id": "inv_01HE2K6BX9C0",
"merchantId": "merchant_acme",
"merchantAddress": "0xA0b8...eB48",
"amount": "4900000",
"currency": "USDC",
"chainKey": "arc-testnet",
"status": "paid",
"settledTxHash": "0x4f1c...92a0",
"settledAt": 1747350522
}
}

Error codes

CodeMeaning
404 not_foundNo invoice exists with that id.
GET/api/invoices

List invoices

Lists invoices, optionally filtered. Supports merchantId and status query parameters. Both are independent — pass either, both, or neither.

Request

GET /api/invoices?merchantId=merchant_acme&status=open

Response

HTTP/1.1 200 OK
Content-Type: application/json
{
"invoices": [
{ "id": "inv_01HE2K6BX9C0", "status": "open", "amount": "4900000" },
{ "id": "inv_01HE2K6BXAA", "status": "open", "amount": "1200000" }
]
}

Error codes

CodeMeaning
400 invalid_statusstatus must be one of draft, open, paid, expired, void.
GET/api/payments

List on-chain payments

Lists payments recorded by the BlockPay indexer. Filter by merchantId, chainKey, or both. Useful for reconciliation against your own ledger.

Request

GET /api/payments?merchantId=merchant_acme&chainKey=arc-testnet

Response

HTTP/1.1 200 OK
Content-Type: application/json
{
"payments": [
{
"id": "pay_01HE2K7Z11",
"invoiceId": "inv_01HE2K6BX9C0",
"chainKey": "arc-testnet",
"txHash": "0x4f1c...92a0",
"amount": "4900000",
"currency": "USDC",
"from": "0xCustomer...",
"to": "0xA0b8...eB48",
"blockNumber": 21893444,
"confirmedAt": 1747350522
}
]
}

Error codes

CodeMeaning
400 invalid_chainchainKey is not a known chain.
POST/api/webhooks/circle

Circle webhook ingestion

Inbound endpoint for Circle's transfer webhooks. The request is HMAC-verified against the shared Circle webhook secret; invalid signatures are rejected with 401. You do not call this endpoint — Circle does.

Request

POST /api/webhooks/circle
Content-Type: application/json
X-Circle-Signature: t=1747350500,v1=<hex hmac sha256>
{
"type": "transfers.created",
"data": {
"id": "ct_01HE2K7Z11",
"amount": { "amount": "49.00", "currency": "USD" },
"destination": {
"type": "blockchain",
"address": "0xA0b8...eB48",
"chain": "ARC"
}
}
}

Response

HTTP/1.1 200 OK
Content-Type: application/json
{ "ok": true }

Error codes

CodeMeaning
401 invalid_signatureHMAC signature did not match.
400 invalid_payloadBody could not be parsed or required Circle fields were missing.