Quick start
Three steps from signed-up to "live on your site." The dashboard's project page generates the exact snippets with your project ID baked in. The blocks below are illustrative.
- Paste the script tag once, in your site's
<head>(or right before</body>). One copy is enough no matter how many buy buttons you have.<script src="https://sdk.coinmoebius.com/v1/coin-moebius-buy.js" crossorigin="anonymous" defer></script> - Add the product to your catalog in the dashboard.Open your project, switch to the Products tab, click "Add product," and set the
product-ref, name, price, and currency. By default Coin Moebius is in strict mode, which means the price you set here is the canonical price (a buyer cannot edit the price in their browser to pay less). - Paste a buy button wherever you want it to appearin the page body. Reference the product by its
product-refin theproduct-idattribute. Repeat with a differentproduct-idandlabelfor each additional product.<coin-moebius-buy project-id="proj_YOUR_ID_HERE" product-id="t-shirt-medium" label="Buy a t-shirt"> </coin-moebius-buy> - Connect at least one provider in the dashboard.Open your project, switch to the Providers tab, click "Add provider," and follow the per-provider steps in Provider setup below.
The buy button
The buy button is a few lines of HTML you paste on any page. No framework, no install step on your end. The button renders on first paint; the provider picker and the payment-provider SDKs load on demand the first time a buyer clicks. It works in any site or framework that renders HTML, including the no-code site builders that let you paste HTML embeds.
Confirmed working with: Angular, Hugo, Jekyll, plain HTML, Webflow (Custom Code embed), Carrd (Embed element), Framer (HTML embed), Squarespace (Code Block), WordPress (Custom HTML block), Ghost, Notion sites (HTML embed widgets). If your site lets you paste HTML on a page, the buy button works there.
Attribute reference
| Attribute | Required | What it accepts | Notes |
|---|---|---|---|
endpoint | No | URL string | Almost always omit this. The button auto-detects its API base: https://api.coinmoebius.com in production, http://localhost:8787 on localhost. Only set it as an override for an unusual setup (e.g., a self-hosted proxy in front of the API). |
project-id | Yes | String, prefixed proj_ | From your project page in the dashboard. Safe to expose publicly, it's an identifier, not a credential. |
product-id | Yes (in strict mode) / Recommended (in ad-hoc mode) | String, any format you choose | Your internal product identifier. In strict mode (the default), this is how the worker looks up the price from your catalog. In ad-hoc mode, it's still passed through to your webhooks in metadata.productId so you can map transactions back to inventory. See the Strict mode vs ad-hoc mode section below. |
amount | Only in ad-hoc mode | Decimal number as string | The price in major units (e.g., dollars, not cents). Must be positive. Omit this in strict mode. The worker reads the canonical price from your catalog and ignores any value in the HTML. Required only when the project is in ad-hoc mode and the product-id is not in the catalog. |
currency | Only in ad-hoc mode | Three-letter ISO 4217 code or your own unit name | USD, EUR, etc. for cards / crypto rails. The pay-by-mail provider accepts any string (e.g., GBK) because the merchant is the one settling the transaction. Same rule as amount: omit in strict mode, required in ad-hoc mode for products not in the catalog. |
label | No | String | The button text. Defaults to Buy. Use this to distinguish buttons on the same page. |
customer-ref | No | String, any format you choose | An opaque identifier for the buyer in your own system, like a logged-in user id. The button forwards it to the worker as metadata.customerRef, and the worker tags the transaction with it. Later you can ask "which of my users paid?" using your own id, without us holding any actual customer data. Omit for anonymous checkouts. |
theme | No | dark or light | Picks the built-in color scheme for the button and popup together. Defaults to dark. Set theme="light" for the light scheme. Your CSS variables override either one. See the styling guide. |
editable-amount | No | true or false | When true, the popup shows an amount field the buyer fills in (tips, donations, pay-what-you-want). Defaults to false, where the amount you set is fixed and no field appears. The payment buttons stay disabled until the amount is a positive number. |
Multiple buttons on one page
The script registers the button once, then any number of instances can render on a page. Each instance is independent. The example below uses strict mode (the default): no amount or currency in the HTML, because the worker looks the price up from each product in your catalog.
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="t-shirt-medium"
label="T-shirt (medium)">
</coin-moebius-buy>
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="mug-blue"
label="Blue mug">
</coin-moebius-buy>If your project is in ad-hoc mode (donation widgets, tip jars), each button also includes amount and currency. See Strict mode vs ad-hoc mode for the full picture.
Theming the button
The buy button renders inside a Shadow DOM, which means none of your page's existing CSS reaches into it. That's deliberate: it keeps the button looking the same on every site, and stops a stray * { box-sizing: ... } rule from breaking the picker modal. You style it through two surfaces from your own stylesheet: CSS custom properties for colors, fonts, and shape, and ::part() selectors for everything else. No JavaScript involved.
Every variable and part is documented on the styling guide, with live examples you can click and copy:
Open the interactive styling guide →
Pricing: fixed vs buyer-priced
Each product in your catalog is either fixed (the price you set is what gets charged) or buyer-priced (the buyer or the embedded HTML picks the amount). The choice is per product, not per project, so a single project can run a catalog of fixed-price downloads next to a tip jar and a "name your own price" donation page on the same site, with one webhook URL and one set of provider credentials.
We never accept a payment for a product that isn't listed in your catalog. There is no project-level "trust anything" escape hatch; every checkout must reference a product you configured.
Why this matters
If the worker trusted the amount attribute on every buy button, a buyer could open their browser's developer tools, edit amount="29.99" to amount="0.01", click Buy, and pay one cent for a $30 product. Fixed-mode products make that impossible.
Fixed mode (the safe default)
New products are created in fixed mode. You set the price in your dashboard's Products tab; the buy element on your site references the product by id. The worker reads the price from your catalog and ignores any amount or currency the buy element might have on it.
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="t-shirt-medium"
label="Buy a t-shirt">
</coin-moebius-buy>A buyer editing the HTML cannot change the price. The catalog wins, every time.
Buyer-priced mode (for tip jars, donations, and pay-what-you-want)
Some products only make sense if the buyer picks the amount: donation widgets, tip jars, name-your-own-price pages. For those, set the product's Pricing to Buyer-priced in the product form. The dashboard shows a safety dialog first because it's a real change in behavior: anyone with browser developer tools can edit the amount before paying. The price you set on the product becomes the suggested default the buy element pre-fills.
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="tip-jar"
amount="5.00"
currency="USD"
label="Leave a tip">
</coin-moebius-buy>The dashboard's per-product snippet generator outputs the right shape automatically, with the suggested amount filled in.
Switching a product between modes
Fixed and buyer-priced are toggled per product, in the product's form. Switching to buyer-priced opens a one-time safety dialog because it's the destructive direction; switching back to fixed is immediate. New checkouts use the new mode straight away; already-completed transactions are unaffected.
Subscriptions
Coin Moebius can sell recurring subscriptions on every fiat provider in your menu. The merchant configures the price and interval once on a product; the buy button on the site stays exactly the same. The payment provider runs the recurring billing, holds the card, retries failed renewals, and hosts the cancellation page. Coin Moebius relays the lifecycle events to your code.
Setting up a recurring product
In the dashboard's Products tab, set the Billing field to Monthly or Annual instead of One-time. An optional Free trial field appears for any trial days you want to grant before the first charge. Save the product.
The buy button HTML doesn't change. The same product-id="pro-plan" attribute works for one-time or recurring; the worker checks the product's billing setting at checkout time and routes through the provider's subscription API when applicable.
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="pro-plan"
label="Subscribe to Pro">
</coin-moebius-buy>What you don't have to build
The provider runs the schedule. We don't store cards. We don't run cron jobs. We don't retry failed charges. We don't send dunning emails. We don't host a cancellation page. That entire side of the system lives inside Stripe (or whichever fiat provider you've connected). You're outsourcing the hard parts of recurring billing to the company that's already running them for millions of merchants.
Subscription events
Renewals and cancellations show up as webhook events your server can react to. Five normalized types cover the lifecycle:
| Event | When it fires |
|---|---|
subscription.created | New signup. Carries the first cycle's amount. |
subscription.renewed | A non-initial cycle succeeded. Extend access through the new period end. |
subscription.payment_failed | A cycle's card was declined. The provider's dunning retries on its schedule; you usually just log this for visibility. |
subscription.canceled | Terminal cancellation. The buyer canceled, dunning was exhausted, or the merchant canceled. |
subscription.updated | Status change, card update, plan change. Inspect the new status. |
Identifying buyers without storing them
Coin Moebius is a payment router, not a customer database. We never store buyer emails, names, addresses, or the provider's internal customer ids. If your application has user accounts (most subscription apps do), pass your own opaque user id as customer-ref on the buy button. The button forwards it to the worker as metadata.customerRef, we thread it through to the provider, surface it back on every event, and store only that opaque string. To us it's meaningless; to you it's the foreign key into your own user system.
<coin-moebius-buy
project-id="proj_YOUR_ID"
product-id="pro-plan"
customer-ref="user_bob_42">
</coin-moebius-buy>When you need deeper buyer detail (email, card last-four, dispute notes), the dashboard gives every transaction a "View in Stripe" link. You click through to the provider, where the buyer record actually lives. We never duplicate it.
Cancellation: link out, don't build
Buyers cancel in the provider's hosted portal: the Stripe Customer Portal, the buyer's PayPal account page, and so on. The portal handles cancellation, card updates, receipt downloads, and plan changes, all UI you don't have to build. You can drop the buyer into the portal with one API call:
const res = await fetch(
`https://api.coinmoebius.com/api/subscriptions/${projectId}/${subId}/portal-url`,
{ method: 'POST', body: JSON.stringify({ returnUrl: 'https://you.example/account' }) },
);
const { url } = await res.json();
window.location.assign(url);Which providers support subscriptions today
Stripe and PayPal work end-to-end through the hosted buy button. Set a product to Monthly or Annual in your dashboard, paste the button on your page, and the click starts a real subscription. The provider runs the renewals.
Square and Authorize.Net don't work through the hosted buy button. Their subscription APIs need the buyer's card collected on your own page, using the provider's JavaScript widget (Square Web Payments SDK, Authorize.Net Accept.js). If you're willing to add that step, the rest of the system (webhook routing, dashboard, status endpoint, customer linking) works exactly the same way it does for Stripe and PayPal subscriptions. See the next section.
Crypto providers (NOWPayments) do not support recurring billing in this product. Recurring crypto is friction-heavy on every gateway we've evaluated; we'd rather ship nothing than ship a half-broken story for it.
Use it without the buy button
The buy button is a convenience layer. Underneath, Coin Moebius is a rented webhook plus a few JSON endpoints. If you'd rather build your own UI, run your own checkout flow, or plug Coin Moebius into something the button can't handle (Square or Authorize.Net subscriptions, a custom payment form, a server-side script, a mobile app), every endpoint the button uses is also callable from anything. No code changes on our side.
This is what's available:
| Endpoint | What it does |
|---|---|
POST /api/checkout/{provider}/{projectId} | Start a checkout. POST { productId, metadata }; receive back whatever the provider needs (a redirect URL for Stripe, an approval URL for PayPal, a token for Authorize.Net Accept Hosted). Render it however you want. |
POST /webhook/{provider}/{projectId} | The provider posts here. We verify the signature, normalize the event, store it, count quota. Point the provider's webhook at this URL whether the original checkout came from our button or your own integration. |
GET /status/{projectId}/{txId} | Poll for the current state of a payment or subscription. Returns the same normalized shape the buy button gets. |
POST /api/subscriptions/{projectId}/{subscriptionId}/portal-url | Generate a provider-hosted portal URL for the buyer to manage their subscription. Works for any provider that has a portal. |
Identify the buyer in your own system by passing metadata.customerRef on the checkout call. It threads through the provider and back on every webhook event, so you can join Coin Moebius's records to your own user database without us storing anything about the buyer.
Why someone uses this path: a static-site builder who wants to write their own button to match their site's design and just needs the webhook handled. A developer who wants to skip the button entirely and call the API from their own server. A merchant who wants to run Square or Authorize.Net subscriptions and is fine wiring up card collection themselves. The button is a starting point. The API is the actual product.
Listening for buyer events
The element fires three browser events. Listen with addEventListener on the element (or on document, the events bubble). All events are cancelable, calling event.preventDefault() stops the default flow.
| Event | When it fires | Detail payload |
|---|---|---|
cm-load-providers | The picker modal is about to ask the API for the list of providers configured on this project. | Empty. |
cm-checkout-started | The buyer picked a provider and Coin Moebius is about to create a checkout session (Stripe / NOWPayments) or generate a reference code (manual). | { provider: 'stripe' | 'nowpayments' | 'manual', ... } |
cm-error | Something failed: network error, signature failure, no provider configured. | { error: Error } |
document.addEventListener('cm-error', (event) => {
console.error('Coin Moebius:', event.detail.error);
// Show your own error UI, send to your analytics, etc.
});There is no cm-success event in the buyer's browser. By the time the payment actually completes, the buyer has been redirected to the payment provider's hosted checkout (Stripe Checkout, NOWPayments invoice page). They return to your site via the success_url you configured (see the next section), and your server learns about the payment via the dashboard or by polling the /status endpoint.
The success and cancel URLs
When you connect Stripe or NOWPayments in the dashboard's Providers tab, you set two URLs:
- Success URL, where the buyer lands after a successful payment. Stripe and NOWPayments append a query parameter (
?session_id=...for Stripe,?NP_id=...for NOWPayments) so your success page can identify which transaction completed. Most static sites just show a generic "Thanks, your payment is processing" message and rely on the dashboard for the source of truth. - Cancel URL, where the buyer lands if they back out before paying. Often the same as your cart page.
Both URLs are configured per-provider in the dashboard, the element doesn't need to know about them.
Provider setup
Stripe
- Sign in to dashboard.stripe.com.
- Publishable key: Developers → API keys → "Publishable key" (starts
pk_live_orpk_test_). - Secret key: same page → "Secret key" → reveal (starts
sk_live_orsk_test_). - Webhook signing secret: Developers → Webhooks → Add endpoint. The endpoint URL is shown on your project page in our dashboard, copy it from there. Subscribe the endpoint to
checkout.session.completed,charge.refunded, andcharge.dispute.created. Stripe shows the signing secret once after creation (startswhsec_). - In our dashboard's Providers tab, click "Add provider" → Stripe, paste all three values plus your Success URL and Cancel URL, save.
Testing: Use Stripe's test-mode keys (they start with pk_test_ and sk_test_) to run test purchases with Stripe's test card numbers. No real money moves. You can also use the "Send a test event" button on the Transactions tab to create a sample Stripe row instantly without involving Stripe at all.
NOWPayments
- Sign in to account.nowpayments.io.
- API key: Account → Store Settings → "API key".
- IPN secret: same page → "IPN Secret Key".
- IPN callback URL: paste the webhook URL from our dashboard's project page into NOWPayments' "IPN callback URL" field.
- In our dashboard, click "Add provider" → NOWPayments, paste the API key and IPN secret plus your Success URL and Cancel URL, save.
Testing: NOWPayments does not offer a sandbox or test mode. To verify the full round trip (checkout, IPN callback, and transaction row in your dashboard), send a real payment using a low-fee coin (like TRX or LTC) at the minimum amount. You can check current minimums on the NOWPayments status page. A few cents confirms everything works end to end. If you want to see what a NOWPayments transaction looks like in your dashboard before spending anything, use the "Send a test event" button on the Transactions tab and pick NOWPayments as the provider.
Pay by mail (manual)
No external accounts, no API keys, no credentials. You provide a mailing address and Coin Moebius generates a unique reference code for each transaction. This works for cash, checks, money orders, Goldbacks, precious metals, or anything else a buyer can put in an envelope.
- In your dashboard, click "Add provider" → Pay by mail.
- Enter your mailing address. This is exactly what the buyer sees when they choose "Pay by mail" in the picker, so use the address where you actually receive packages.
- Enter the expected currency. This is free-form: type
USDfor cash or checks,GBKfor precious metals, or whatever unit makes sense for what you're accepting. You're the one opening the envelope, so you decide what counts. - Save. The buyer can now pick "Pay by mail" in the picker alongside any other providers you've connected (Stripe, NOWPayments, etc.) and get a unique reference code like
X2M-K9P-R7QWto include with their shipment.
You don't need separate buy buttons for different payment methods. The picker handles the choice. A single button on your page can offer cards, crypto, and pay-by-mail at once.
Transaction statuses
Each transaction row in the dashboard has one of these statuses. The status reflects the most recent event for that transaction; refunds and disputes update the existing row rather than inserting a sibling.
| Status | Meaning |
|---|---|
succeeded | Payment completed. Money is in your account (net of provider fees). |
pending | Payment is in flight. Common with async payment methods (ACH, some crypto rails) where confirmation takes minutes to hours. |
failed | Payment did not complete: card declined, expired auth, hard rejection. No money moved. |
partial | Buyer paid less than invoiced (common when a crypto buyer sends a network-fee-reduced amount). The row's amount reflects what was actually received; check metadata.invoicedAmount for what was requested. |
refunded | Money has been returned to the buyer, fully or partially. The row's amount is the refunded amount; refunds can happen days or weeks after the original payment. |
disputed | The buyer (or their bank) opened a dispute or chargeback. Check the provider's interface for the response window. The row's metadata.reason carries the provider's classification verbatim. |
pending_manual | A pay-by-mail transaction is awaiting your physical confirmation. See the next section. |
manual_canceled | You clicked Cancel on a pending pay-by-mail row before the buyer's payment arrived. |
manual_expired | A pending pay-by-mail row sat for 30 days without confirmation and auto-expired. |
manual_revoked | You confirmed receipt of a pay-by-mail payment but later undid the confirmation (e.g., the payment turned out to be invalid). The row moves from succeeded back to this terminal state. |
The pay-by-mail flow
- The buyer clicks the Coin Moebius button on your site and picks "Pay by mail" in the picker. They see a preview first: your mailing address, the amount due, and any instructions you set, with a <strong>"Confirm, I'll send payment"</strong> button. Nothing is recorded yet, and there is no reference code at this stage.
- The buyer clicks "Confirm, I'll send payment." That is the step that creates the transaction: Coin Moebius generates a unique reference code like
X2M-K9P-R7QWand shows it alongside your mailing address. The buyer puts the reference code inside the package or on the check. - Once they confirm, a row appears in your dashboard's Transactions tab with status
pending_manual. The top of the list also shows a callout reminding you that mailed-payment confirmations are waiting. - When the buyer's payment arrives in your mailbox (cash, check, Goldback, whatever the expected currency was), type the reference code into the search box on the Transactions tab to find the matching row. Click Mark received. The buyer's success URL is fired (so they get a confirmation page), and you proceed to ship whatever they bought.
- If the buyer's payment never arrives, the row auto-expires after 30 days. You can also Cancel a row manually before that.
- If you confirmed receipt but later discover the payment was invalid (counterfeit, wrong item, empty envelope), click Undo confirmation on the row. The status changes to
manual_revoked. This corrects your records in the dashboard. Communicating with the buyer about what happened next is between you and them.
Testing pay-by-mail
Pay-by-mail has no sandbox, no test keys, and no external account to configure. The entire flow happens between your site and your dashboard. There are two ways to test it: the full round trip from a buy button on a page, or a quick test straight from the dashboard.
Quick test from the dashboard
The dashboard can create a test transaction for any provider without involving a real payment. This is useful for seeing what a transaction row looks like, practicing the "Mark received" flow for pay-by-mail, or previewing how a crypto payment appears in your list before spending real money.
- Open the Transactions tab on your project and click Send a test event.
- Pick a product, choose what happened (payment, refund, pay-by-mail, subscription renewal), and pick the provider you want to simulate (Stripe, NOWPayments, or Pay by mail). For pay-by-mail events, the provider is set automatically.
- Click Send test event. A new row appears tagged "test" so it doesn't mix with live data. The row shows up under the correct provider filter, just like a real transaction would.
For pay-by-mail specifically, choose "A pay-by-mail order is waiting" to create a pending_manual row with a real reference code. You can then practice clicking "Mark received," "Cancel," and "Undo confirmation" on it.
Test events don't count toward your monthly transaction quota.
Full round trip from a buy button
To test the complete flow (what your buyer sees through what you see), walk through these steps. The whole thing takes about two minutes.
- Set up the provider. In your dashboard, go to your project's Providers tab and click "Add provider" → Pay by mail. Enter any mailing address (your own is fine for testing) and any currency (e.g.,
USD,GBK). Save. - Add a product. In the Products tab, create a product with a name like "Test item" and a price. Set the currency to match what you entered for the provider.
- Put the buy button on a page. Paste the buy button HTML on any page, even a bare HTML file on your computer. Point the
endpointattribute at your Cloud project URL. If you're running the worker locally, usehttp://localhost:8787instead. - Click the button and pick "Pay by mail." The picker opens showing every provider you've connected. Pick "Pay by mail." You'll see a preview with your mailing address, the expected amount and currency, and a "Confirm, I'll send payment" button. No reference code yet.
- Click "Confirm, I'll send payment." This is the buyer committing to send payment. Coin Moebius records the transaction and shows a reference code like
X2M-K9P-R7QW. - Open the dashboard. Go to the Transactions tab. A new row appears with status
pending_manualand the same reference code you just saw. If this is your first pending row, a callout at the top of the list reminds you that mailed payments are waiting for confirmation. - Click "Mark received." The row's status changes to
succeeded. That's the complete happy path, from the buyer's click to your confirmation.
Finding a transaction by reference code
When a payment arrives in your mailbox, look for the reference code the buyer included (printed on the envelope, written on the check memo, or tucked inside the package). Then type that code into the search box on the Transactions tab and press Enter. The table filters to matching rows so you can click "Mark received" on the right one without paging through your full transaction history.
Testing the other outcomes
Run through the same steps again, but instead of marking the row received, try each of these:
- Cancel it. Click "Cancel" on the pending row. The status changes to
manual_canceled. This is what you'd do when a buyer tells you they changed their mind before mailing anything. - Let it expire. Leave a pending row alone. After 30 days it auto-expires to
manual_expired. You don't need to wait 30 days to verify the behavior. Expiration works the same as Cancel, except it happens automatically on a schedule. - Enter a different amount. When clicking "Mark received," enter a received amount that differs from what was expected (e.g., the buyer sent 4 Goldbacks instead of 5). The transaction still succeeds, but the dashboard shows both the expected and received amounts so your records reflect what actually arrived.
- Undo a confirmation. First mark a row received so it shows
succeeded, then click "Undo confirmation" on that row. The status changes tomanual_revoked. This is how you'd correct a mistake or flag a payment that turned out to be invalid after the fact.
What to look for
- The reference code in the picker matches the reference code on the dashboard row.
- Your mailing address displays correctly in the picker.
- The "Mark received" and "Cancel" buttons appear only on
pending_manualrows, not on card or crypto transactions. - After marking received, the row shows
succeededwith the correct amount. - Searching by reference code in the search box finds the right row.
Full round-trip transactions created during testing count toward your monthly quota. On the free tier (100 transactions/month), a handful of test runs won't make a dent. Test events sent from the dashboard's "Send a test event" button don't count at all.
Refunds, disputes, partial payments
Coin Moebius listens for these provider events and updates the original transaction row in the dashboard. Specifically:
- Refunds: Stripe's
charge.refundedevent (full or partial refunds, includingamount_refundedso partial refunds show the slice that was returned). NOWPayments'refundedIPN. The row's status flips torefunded, theamountreflects the refunded amount, andmetadata.originalChargeId/metadata.originalAmountcarry the original payment context. - Disputes (chargebacks): Stripe's
charge.dispute.createdevent. The row's status flips todisputed. The provider's stated reason passes through tometadata.reasonverbatim, you can render it in your own UI however you choose. - Partial payments: NOWPayments'
partially_paidIPN. The row's status flips topartial. Theamountreflects what was actually received (actually_paid);metadata.invoicedAmountis what was requested.
In v1, these events are surfaced in the dashboard and via the /status endpoint. A future release will add email notifications and an outbound webhook forwarder so your own backend can react automatically. For now, set up a small polling job (see the next section) or check the dashboard.
Polling from your backend
For a server-side source of truth, poll GET /status/:projectId/:txId from your backend. The response shape:
{
"status": "succeeded",
"amount": 29.99,
"currency": "USD",
"isTest": false,
"createdAt": "2026-05-14T01:04:21.000Z",
"updatedAt": "2026-05-14T01:04:21.000Z"
}Status values follow the same enum as the dashboard (see Transaction statuses). The endpoint is unauthenticated but rate-limited to 60 requests / minute per IP. The transaction id you pass is whatever the SDK returned in the cm-checkout-started event or what's shown in the dashboard's Reference column.
A typical pattern: when your success_url page loads, kick off a backend job that polls /status/:projectId/:txId every 15 seconds until it sees succeeded (or failed / a timeout), then fulfill the order.