Integrations
What connects today, and what does not
Available now
Code exists, is covered by tests in the repository, and runs on testnets.
- AvailableSee it in the demo
Checkout page
A payment page with two methods: pay with a connected wallet (gasless, one signature) or send USDC to a unique address. Status comes from the backend only.
- AvailableUse cases
Payment links
Reusable fixed-amount links at /l/{slug}. Opening one creates a fresh payment.
- AvailableUse cases
Invoices
Numbered invoices with line items and a due date, shared as a link. They are not emailed by Plinth.
- AvailableDevelopers
REST API and OpenAPI
Versioned under /v1 with an OpenAPI 3.1 document generated from the same route definitions the server registers. Exact decimal amounts, structured errors, idempotency keys.
- AvailableWebhook guide
Signed webhooks
13 event types, HMAC-SHA256 signature, 8 retries over about 10 hours, a delivery log and manual retry.
- AvailableSecurity
API keys and team roles
Hashed test and live keys, rotation, four team roles and an audit log.
In progress
Groundwork is in the repository, but it is not usable end to end.
- In progressNetworks
Arc Testnet and Base Sepolia
Two testnets configured with the official USDC and probed. Contracts for Base Sepolia are not deployed yet.
- In progress
Cross-chain consolidation (CCTP)
Circle CCTP domains are recorded per network. The transfer lifecycle is designed but not implemented.
- In progress
KMS or HSM relayer signer
Signing goes through a SignerProvider interface. Only an environment-key implementation exists.
Planned
Noted as a limitation or design in the repository. No committed date.
- Planned
E-commerce plugins
No plugin code exists in the repository. Today you integrate through the API.
- Planned
Official SDKs
No SDK exists. The OpenAPI document can be used to generate a client.
- Planned
WalletConnect and mobile wallets
Checkout discovers browser-injected wallets only (EIP-6963). Mobile wallets can use the address flow.
- Planned
Smart-contract wallet payers
Contract wallets need an EIP-1271 path on the gasless route. They can use the address flow today.
- Planned
Email delivery
Invoices and receipts are shared as links; the platform sends no email.
- Planned
Solana
Would be a separate adapter, since it cannot reuse CREATE2 or EIP-3009. Not started.
- Planned
Gateway Nanopayments
A different model from invoice checkout. Support on the target networks is unconfirmed, so nothing is integrated.
Connect in two calls
Create a payment, then verify and act on the webhook. Replace PLINTH_API with your API host and use an sk_test_ key on testnets.
# 1. Create a payment (idempotent)
curl -X POST $PLINTH_API/v1/payment-intents \
-H "Authorization: Bearer $PLINTH_KEY" \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{ "amount": "25.00", "order_reference": "order-1042" }'
# 2. Register a webhook endpoint (the secret is shown once)
curl -X POST $PLINTH_API/v1/webhook-endpoints \
-H "Authorization: Bearer $PLINTH_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://YOUR_DOMAIN/plinth/webhook", "events": ["payment.confirmed", "payment.completed"] }'import crypto from "node:crypto";
import express from "express";
// Create a payment and send the customer to payment.checkout_url
const res = await fetch(process.env.PLINTH_API + "/v1/payment-intents", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.PLINTH_KEY,
"Idempotency-Key": "order-1042",
"Content-Type": "application/json",
},
body: JSON.stringify({ amount: "25.00", order_reference: "order-1042" }),
});
const payment = await res.json();
// Verify the signature on the raw body, then fulfil on payment.confirmed
const app = express();
app.post("/plinth/webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = Object.fromEntries(
(req.get("Plinth-Signature") ?? "").split(",").map((p) => p.split("=")),
);
const mac = crypto
.createHmac("sha256", process.env.PLINTH_WEBHOOK_SECRET)
.update(parts.t + "." + req.body)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const ok = fresh && mac.length === (parts.v1 ?? "").length &&
crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(parts.v1));
if (!ok) return res.sendStatus(400);
const event = JSON.parse(req.body.toString());
// De-duplicate on event.id, then act on event.type
res.sendStatus(200);
});These samples follow docs/api-guide.md and the reference verifier in the repository. They are illustrative and have not been published as an SDK.
Missing an integration you need?
Planned items have no dates. Knowing which one blocks you helps decide what to build first.