Skip to main content
← API reference

Quickstart

Three steps take a shop from nothing to a safe order: embed the configurator, verify the quote on your server before the cart, and accept the signed webhook. The embed makes the shop-page calls for you; your code only does the server side.

Base URL https://cfg.cutoptim.com · Contract version v1 · OpenAPI 3.1 document

Early access: the merchant API is built and can be tried in a sandbox shop; plug-ins for shop platforms are still in development. CutOptim collects no payment: the cart, the checkout and the payment stay in your shop.

Paths, field names, error codes, header names and code samples stay in English: they are the contract.

1. Embed the configurator

Add the script and the element to the product page, with your publishable key (pk_live_…, or a pk_test_… test key while you build). The page's origin must be on the key's allowed origins. The embed then calls GET /cfg/v1/config/public, POST /cfg/v1/preview, POST /cfg/v1/quote and POST /cfg/v1/order itself.

When the shopper orders, the service stores the signed order and the element raises the cutoptim:order browser event with { orderId, order, idempotencyKey }. Treat that event as a hint for your page, never as proof of a price.

<script type="module" src="https://cfg.cutoptim.com/embed/assets/cutoptim-configurator.js"></script>

<button data-cutoptim-open="#cut">…</button>
<cutoptim-configurator id="cut" mode="modal" key="pk_live_your_publishable_key" lang="en">
</cutoptim-configurator>

<script>
  document.getElementById('cut').addEventListener('cutoptim:order', (e) => {
    // e.detail = { orderId, order, idempotencyKey } - send it to YOUR server, which verifies it
  });
</script>

2. Verify on your server before the cart

Your server sends the quote token to POST /cfg/v1/quote/verify with the secret key. The answer is valid plus redeemed; a refusal is still a 200 with valid: false and a reason. Then it looks up the order the browser named with GET /cfg/v1/orders/{id} and compares the plan hash, the quote hash, the currency and the totals with the verified token. Only then does it create the cart line, at the verified price.

curl -X POST https://cfg.cutoptim.com/cfg/v1/quote/verify \
  -H "Authorization: Bearer $CUTOPTIM_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"quoteToken":"<order.quote.quoteToken>"}'
# {"valid":true,"redeemed":true,"claims":{…},"test":false}

curl https://cfg.cutoptim.com/cfg/v1/orders/<orderId> \
  -H "Authorization: Bearer $CUTOPTIM_API_SECRET"
# {"orderId":"…","status":"received","priced":true,"planHash":"…","quoteHash":"…","totals":{…}}

3. Receive the signed webhook

Publish a webhook endpoint in your shop configuration. Every live order sends order.created, every status change order.status_changed. Read the raw body, check the CutOptim-Signature header, then store the order once per CutOptim-Event-Id. The receiver below is the reference example, unchanged.

// example-server.mjs - a webhook endpoint on node:http, to read and adapt.   CUTOPTIM_WEBHOOK_SIGNING=whsec_... node example-server.mjs
// (whsec_REPLACE_ME is a placeholder: the real secret comes from the CutOptim admin route.)
import { createServer } from 'node:http';
import { createWebhookReceiver } from './cutoptim-verify.mjs';

const secret = process.env.CUTOPTIM_WEBHOOK_SIGNING;
if (!secret) { console.error('set CUTOPTIM_WEBHOOK_SIGNING (whsec_REPLACE_ME is a placeholder)'); process.exit(1); }

const receive = createWebhookReceiver({
  secrets: [secret],
  async onEvent(event) {
    // event.type === 'order.created'; event.data.order is the signed order document. Store it in YOUR order table here,
    // keyed by event.data.orderId, in the same transaction that records the event id as seen.
    console.log('order.created', event.data.orderId);
  },
});

createServer(async (req, res) => {
  if (req.method !== 'POST') { res.writeHead(405).end(); return; }
  const chunks = []; let size = 0;
  for await (const c of req) { size += c.length; if (size > 2_000_000) { res.writeHead(413).end(); return; } chunks.push(c); }
  const out = await receive({ rawBody: Buffer.concat(chunks), headers: req.headers });   // the RAW bytes, not parsed JSON
  res.writeHead(out.status, { 'Content-Type': 'application/json' }).end(JSON.stringify({ result: out.reason }));
}).listen(Number(process.env.PORT ?? 8787));

Next

Keys, test keys and origins: Keys and authentication. The full receiver, the PHP version and the retry schedule: Webhooks. A step-by-step version for your own platform: Custom shop or ERP.

There is no "try it" button here on purpose: the secret key belongs on your server, never in a browser. The demo shop shows the embed and the order hand-off working end to end.

LegalConfigurator terms ·Data processing agreement ·Shopper privacy notice ·Third-party licences