@opaqueprivacy/merchant-sdk
v0.1.0-alpha.0
Published
Opaque Pay Merchant SDK — accept private crypto payments on your site.
Downloads
96
Maintainers
Readme
@opaqueprivacy/merchant-sdk
Drop-in SDK for accepting private crypto payments on your site. The amount and sender are hidden on-chain; you still get a signed receipt and a webhook when payment lands.
v1 alpha. APIs may change. Don't rely on this for production revenue yet — treat early integrations as demos. File issues at the main repo.
Install
npm install @opaqueprivacy/merchant-sdkGet an API key
- Go to the Opaque dashboard → Merchant SDK tab.
- Click Generate key and copy the
op_live_...value. It's shown exactly once. - Paste it into your server's env as
OPAQUE_API_KEY.
You also need to have made at least one deposit on Opaque from the wallet you generated the key under — that provisions the intermediate wallet that receives payments.
Server: create a checkout
import { OpaquePay } from "@opaqueprivacy/merchant-sdk";
const opaque = new OpaquePay({ apiKey: process.env.OPAQUE_API_KEY! });
const checkout = await opaque.createCheckout({
amount: 9.99,
serviceId: "order_1234", // your own order ID
expirationHours: 1,
callbackUrl: "https://shop.com/webhooks/opaque",
});
// Send the customer to checkout.url, or hand the URL to the browser widget.Browser: drop-in widget
<button id="pay">Pay with Opaque</button>
<script type="module">
import { openCheckout } from "@opaqueprivacy/merchant-sdk/browser";
document.getElementById("pay").onclick = async () => {
// Ask your server to create a checkout, then open the URL.
const { url } = await fetch("/api/create-checkout", { method: "POST" })
.then((r) => r.json());
try {
const result = await openCheckout({ url });
console.log("paid", result.transactionSignature);
} catch (err) {
// CheckoutCancelledError if the user closed the popup
}
};
</script>The widget opens /pay/<id> in a popup, listens for a postMessage from the Opaque gateway, and resolves with { requestId, transactionSignature, amount, token } when the on-chain settle lands.
Server: verify the webhook
When the payment lands, Opaque POSTs a JSON event to your callbackUrl and signs it with an ed25519 key. Always verify — anyone could POST anything to your endpoint, but only Opaque can sign with the gateway key.
import express from "express";
import { OpaquePay } from "@opaqueprivacy/merchant-sdk";
const opaque = new OpaquePay({ apiKey: process.env.OPAQUE_API_KEY! });
const app = express();
// Capture the raw body — needed for signature verification.
app.post(
"/webhooks/opaque",
express.raw({ type: "*/*" }),
(req, res) => {
const sig = req.headers["x-opaque-receipt-signature"] as string;
try {
const event = opaque.verifyWebhook(req.body.toString("utf8"), sig);
// event.requestId, event.amount, event.transactionSignature, …
// Mark order paid in your DB here.
res.json({ ok: true });
} catch (err) {
res.status(400).json({ error: (err as Error).message });
}
},
);The webhook is best-effort, single attempt in v1 alpha. If your server is down when the gateway tries, the payment still settles on-chain — you can pull the latest state with opaque.retrieveCheckout(id) and reconcile.
Reference
new OpaquePay({ apiKey, baseUrl?, fetch? })
apiKey— required.op_live_...from the dashboard.baseUrl— defaults tohttps://opaque.privacy.fetch— optional custom fetch (defaults to global).
opaque.createCheckout(input)
| Field | Type | Notes |
| ----------------- | -------- | -------------------------------------------------- |
| amount | number | Human units (9.99 = 9.99 USDC). Required. |
| serviceId | string | Your own order/cart ID. Max 200 chars. Required. |
| expirationHours | number | Default 24, max 720. |
| callbackUrl | string | https:// only. Required if you want a webhook. |
Returns a Checkout with id, url, status, expiresAt, etc.
opaque.retrieveCheckout(id)
Returns the latest state of a checkout. Use this to reconcile if a webhook is missed.
opaque.verifyWebhook(rawBody, signatureHeader)
Verifies the ed25519 receipt signature against the gateway's public key. Returns the parsed WebhookEvent if valid, throws otherwise.
openCheckout({ url, width?, height?, timeoutMs? }) (browser)
Opens the checkout URL in a popup, resolves when the customer pays. Rejects with CheckoutCancelledError if they close the window.
Example
A full Express app is in examples/express/. Run it with:
OPAQUE_API_KEY=op_live_xxx node examples/express/index.jsThen visit http://localhost:3000/pay.html.
Security notes
- Never embed your API key in client-side code. Always create checkouts from your server.
- Always verify webhook signatures. Webhooks come from the public internet — only the signature proves they came from Opaque.
- The plaintext API key is shown once at creation. Revoke and rotate from the dashboard.
