Shieldz API docs
Accept crypto, settle to your own wallet. Create an invoice, send your customer to a hosted checkout, and get a signed webhook the moment it's paid. Base URL: https://shieldz.cash
Overview
Shieldz is a non-custodial crypto payment API. Funds settle straight to a wallet you control; Shieldz only ever sees a public address, so there is nothing to freeze or skim, and there is no platform fee (network gas only). A full integration is the four steps in the diagram above.
| Mode | Setup | Best for |
|---|---|---|
| API key | An sk_live_ key and signed webhooks. Also unlocks Bitcoin and shielded Zcash. | Servers and stores. |
| Keyless | Just a wallet address, no account, no key. Payment links, tip jars, an MCP server for AI agents, or a plain GET https://shieldz.cash/api/v1/tip-jars?to=0x…&title=… URL. | Agents, zero-setup links. |
This page documents API-key mode. For keyless mode and AI agents, see the agents guide.
Official SDK Node / TypeScript
Prefer a typed client? The official SDK wraps this API with invoice helpers, webhook signature verification, automatic retries, and auto-pagination, zero dependencies, runs on Node 18+, Deno, Bun, and edge runtimes.
npm install @shieldz/sdk
import Shieldz, { constructEvent } from "@shieldz/sdk";
const shieldz = new Shieldz(process.env.SHIELDZ_API_KEY);
// Create an invoice, send the buyer to invoice.pay_url
const invoice = await shieldz.invoices.create({
amount_usd_cents: 2500,
memo: "Order #1234",
metadata: { order_id: "1234" },
});
// Verify a webhook against the raw body, then act on it
const event = await constructEvent(rawBody, signatureHeader, WEBHOOK_SECRET);
if (event.type === "invoice.paid") fulfill(event.data.invoice);
Source, examples & issues: github.com/ShieldZCash/shieldz-sdk. Also: a browsable API reference, the OpenAPI spec, and a Postman collection. Prefer raw HTTP? The full REST reference below works from any language.
1Get an API key
Create one in your dashboard under Developer mode → API keys (merchant.shieldz.cash). Keys are shown once, store them securely.
sk_live_… for production, sk_test_… for test mode (simulated settlement, real webhooks). Authenticate every request with Authorization: Bearer <key>.
2Create an invoice
POST /api/v1/invoices
curl https://shieldz.cash/api/v1/invoices \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"amount_usd_cents": 2500,
"memo": "Order #1234",
"customer_email": "[email protected]",
"metadata": { "order_id": "1234" }
}'
Only amount_usd_cents is required. Optional: memo, customer_email, metadata (any JSON, echoed back on webhooks), expires_in_seconds, and idempotency_key (safely retry creates).
Response:
{
"id": "R1uykKHSKVXc3Qb5x2T9",
"object": "invoice",
"amount_usd_cents": 2500,
"status": "pending",
"pay_url": "https://shieldz.cash/pay/R1uykKHSKVXc3Qb5x2T9",
"expires_at": 1750000000000,
"mode": "live"
}
3Send your customer to checkout
Redirect the buyer to the pay_url from the response, a hosted page that handles coin selection, the live amount, and payment detection. You build nothing.
https://shieldz.cash/pay/{invoice.id}
4Get notified when it's paid webhooks
Set your endpoint URL in Developer mode → Webhooks. Shieldz POSTs a JSON event on every state change:
| Event | When |
|---|---|
invoice.paid | Payment detected & sufficient, fulfill the order. |
invoice.failed | Payment failed / refunded, do not fulfill. |
invoice.expired | Window elapsed with no payment. |
{
"type": "invoice.paid",
"created": 1750000000,
"data": {
"invoice": {
"id": "R1uykKHSKVXc3Qb5x2T9",
"status": "paid",
"amount_usd_cents": 2500,
"metadata": { "order_id": "1234" },
"paid_after_expiry": false
}
}
}
Verify the signature on every webhook, it's how you know it's really from Shieldz:
import { createHmac, timingSafeEqual } from "node:crypto";
// Shieldz signs every webhook: X-Shieldz-Signature: t=<unix>,v1=<hex>
// where hex = HMAC_SHA256(your_signing_secret, "<t>.<raw_request_body>").
const TOLERANCE_SECONDS = 300; // reject events older than 5 min
function verifyShieldzWebhook(rawBody, header, secret) {
const parts = header.split(",");
const t = parts.find(p => p.startsWith("t="))?.slice(2);
if (!t) return false;
// 1) Replay guard: t is INSIDE the signed payload (so it can't be forged), // reject anything too old to stop a captured event being re-sent later.
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > TOLERANCE_SECONDS) {
return false;
}
// 2) Recompute the HMAC and constant-time compare. During key rotation the
// header may carry multiple v1= values, accept any match.
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const sigs = parts.filter(p => p.startsWith("v1=")).map(p => p.slice(3));
return sigs.some(s =>
s.length === expected.length &&
timingSafeEqual(Buffer.from(s), Buffer.from(expected))
);
}
// IMPORTANT: verify against the RAW request body bytes, before JSON.parse.
// Also dedupe on the X-Shieldz-Delivery header (at-least-once delivery), that
// + the timestamp check together fully close replay.
X-Shieldz-Delivery header. The X-Shieldz-Event header carries the event type.Or check a payment yourself polling
Don't want to run a webhook listener? Retrieve any invoice on demand and read its status. Same call powers reconciliation jobs and "did this pay?" checks.
GET /api/v1/invoices/{id}
curl https://shieldz.cash/api/v1/invoices/R1uykKHSKVXc3Qb5x2T9 \
-H "Authorization: Bearer sk_live_your_key"
{
"id": "R1uykKHSKVXc3Qb5x2T9",
"object": "invoice",
"status": "paid", // pending | paid | expired | failed
"paid_at": 1750000000000, // null until paid
"amount_usd_cents": 2500,
"expires_at": 1750000000000
}
status is one of pending, paid, expired, failed; paid_at is set once it settles. Poll politely (every few seconds with backoff) until paid/expired, invoices carry an expires_at so you know when to stop.
Chains & assets
Pick where funds settle with settlement on the invoice. Stablecoins work in every mode; Bitcoin and shielded Zcash need an API key.
| Asset | Chains | Modes |
|---|---|---|
USDC | Base, Arbitrum, Optimism, Polygon, Ethereum | All |
USDT | Base, Arbitrum, Optimism, Polygon, Ethereum | All |
BTC | Bitcoin | API key |
ZEC (shielded) | Zcash | API key |
Errors & rate limits
Every error returns a JSON body with a stable type and code you can branch on, plus a human message and, for validation errors, the offending param.
{
"error": {
"type": "invalid_request",
"code": "missing_field",
"message": "amount_usd_cents is required",
"param": "amount_usd_cents"
}
}
| Status | type | Meaning |
|---|---|---|
400 | invalid_request | Malformed or missing parameters. |
401 | auth_error | Missing or invalid API key. |
403 | invalid_request | Key not allowed for this action. |
404 | invalid_request | No such invoice or resource. |
409 | conflict | Idempotency key reused with a different body. |
429 | rate_limit | Too many requests, back off and retry. |
500 | internal_error | Transient, retry with backoff. |
Send an idempotency_key on POST /api/v1/invoices to make retries safe, the same key returns the original invoice instead of creating a duplicate.
Test mode
Use an sk_test_… key to create test invoices: settlement is simulated, but webhooks fire for real so you can build and verify your integration end-to-end before going live.
Full example Bun
Everything above wired together, create an invoice, redirect to checkout, and verify + handle the webhook (with replay protection and idempotency). Copy-paste runnable:
// Bun, full Shieldz integration. Zero dependencies (node:crypto is built in).
// Run: SHIELDZ_API_KEY=sk_live_... SHIELDZ_WEBHOOK_SECRET=whsec_... bun run server.ts
import { createHmac, timingSafeEqual } from "node:crypto";
const SHIELDZ_API = "https://shieldz.cash";
const API_KEY = process.env.SHIELDZ_API_KEY; // sk_live_... (or sk_test_)
const WEBHOOK_SECRET = process.env.SHIELDZ_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;
const seen = new Set(); // dedupe delivery ids, use Redis/DB in production
Bun.serve({
port: 3000,
async fetch(req) {
const { pathname } = new URL(req.url);
// 1. Create an invoice, send the buyer to the hosted checkout.
if (req.method === "POST" && pathname === "/checkout") {
const resp = await fetch(SHIELDZ_API + "/api/v1/invoices", {
method: "POST",
headers: {
"Authorization": "Bearer " + API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount_usd_cents: 2500,
memo: "Order #1234",
metadata: { order_id: "1234" }, // echoed back on the webhook
}),
});
if (!resp.ok) return new Response("could not create invoice", { status: 502 });
const invoice = await resp.json();
return Response.redirect(invoice.pay_url, 303); // hosted Shieldz checkout
}
// 2. Receive webhooks, verify against the RAW body bytes (req.text()).
if (req.method === "POST" && pathname === "/webhooks/shieldz") {
const rawBody = await req.text();
const sig = req.headers.get("X-Shieldz-Signature") ?? "";
if (!verifyShieldzWebhook(rawBody, sig, WEBHOOK_SECRET)) {
return new Response("invalid signature", { status: 400 });
}
const deliveryId = req.headers.get("X-Shieldz-Delivery");
if (deliveryId && seen.has(deliveryId)) return new Response("ok"); // replay/dupe
if (deliveryId) seen.add(deliveryId);
const event = JSON.parse(rawBody);
if (event.type === "invoice.paid") {
fulfillOrder(event.data.invoice.metadata?.order_id);
}
return new Response("ok"); // ack fast; do slow work async
}
return new Response("not found", { status: 404 });
},
});
function verifyShieldzWebhook(rawBody, header, secret) {
const parts = header.split(",");
const t = parts.find(p => p.startsWith("t="))?.slice(2);
if (!t) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
const sigs = parts.filter(p => p.startsWith("v1=")).map(p => p.slice(3));
return sigs.some(s => s.length === expected.length &&
timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}
function fulfillOrder(orderId) {
// Your logic: mark the order paid, ship it, grant access...
console.log("fulfilled order", orderId);
}
console.log("Shieldz example listening on :3000");
Reference & SDKs
| Resource | Link |
|---|---|
| Browsable API reference | shieldz.cash/reference |
| OpenAPI spec | /openapi.json |
| Postman collection | /shieldz.postman_collection.json |
| Official SDKs (Node, Python, Rust, PHP) | github.com/ShieldZCash/shieldz-sdk |
| AI agents & MCP | shieldz.cash/agents |