From nothing to a live recurring subscription. Everything below runs in test mode with the keys already in your dashboard — no bank connection required.
A product is what you sell; a price says how much and how often. One call does both — pass a name with no product and the product is created alongside the price. Add a second price later (a yearly one, say) with POST /v1/plans and the product’s id.
curl -X POST https://api.ronda.sh/v1/plans \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro monthly",
"amount": 12900,
"currency": "AMD",
"interval": "month",
"trial_days": 14
}'
# → { "id": "plan_9f3a…", "object": "plan", "product": "prod_6f21…", "amount": 12900, … }This is the only way a card enters the system. Ronda returns a URL; send your customer there. The card is typed on your bank's page, not yours.
curl -X POST https://api.ronda.sh/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"type": "subscription",
"plan": "plan_9f3a…",
"customer_email": "anna@example.am",
"success_url": "https://yourapp.am/welcome"
}'
# → { "id": "cs_1b4c…", "url": "https://ronda.sh/checkout/cs_1b4c…", … }Redirect to url. Ronda shows the amount and your business name, then forwards to the bank for the card and 3-D Secure. When the customer comes back, Ronda asks the bank what happened, stores the card token, and creates the subscription.
Do not rely on the browser returning — a customer can close the tab. Add a webhook endpoint and act on the event.
curl -X POST https://api.ronda.sh/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.am/ronda/webhook"}'
# → { "id": "we_…", "secret": "whsec_…" } ← shown once// Node — verify, then act
import { createHmac, timingSafeEqual } from "node:crypto";
app.post("/ronda/webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.headers["ronda-signature"];
const parts = Object.fromEntries(header.split(",").map(kv => kv.split("=")));
const body = req.body.toString();
// Reject anything older than five minutes: that is the replay guard.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return res.status(400).end();
const expected = createHmac("sha256", process.env.RONDA_WEBHOOK_SECRET)
.update(`${parts.t}.${body}`)
.digest("hex");
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) return res.status(400).end();
const event = JSON.parse(body);
if (event.type === "checkout.session.completed") grantAccess(event.data.object.customer);
if (event.type === "subscription.canceled") revokeAccess(event.data.object.customer);
res.json({ received: true });
});Nothing else is needed. At the end of each period Ronda charges the saved card, merchant-initiated, with no 3-D Secure and nobody present. You will receive payment.succeeded, or subscription.past_due followed by up to three retries.
To see a renewal without waiting a month, open the subscription in the dashboard and use the simulation control, or set a plan with interval: "day" while you are testing.
If you would rather run your own billing logic, the same engine is available directly.
curl -X POST https://api.ronda.sh/v1/payments \
-H "Authorization: Bearer sk_test_…" \
-H "Idempotency-Key: invoice-8891" \
-H "Content-Type: application/json" \
-d '{"amount": 45000, "customer": "cus_…", "description": "September"}'subscription.past_due — that is a customer whose card failed, still recoverable.