Developer documentation

Add checkout
in two steps.

One server route. One browser script. Your secret stays on your server.

Two required stepsStable yeahco.jsOptional styling
Before you start

Complete this once.

01

Activate

Activate your Merchant, MID, TID and at least one payment method.

02

Copy secrets

Store the live API secret and webhook signing secret on your server.

03

Add URLs

Register your checkout website origin and HTTPS webhook URL.

Step 1 / Server

Create one server route.

This example uses Node and Express. Load the order from your database; never accept its amount from browser code.

Colored values come from your store or Yeah Connect account.

server.js
app.post("/api/create-checkout-session", async (req, res) => {
  const order = await loadVerifiedOrder(req); // Your database

  const response = await fetch(
    "https://yeahconnect.com/v1/checkout/sessions",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.YEAHCONNECT_SECRET}`,
        "Idempotency-Key": order.id,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        amount: order.amountInCents,
        currency: "SGD",
        reference: order.id,
        successUrl: "https://shop.example.com/payment/complete",
        cancelUrl: "https://shop.example.com/checkout"
      })
    }
  );

  const data = await response.json();
  if (!response.ok) return res.status(response.status).json(data);
  res.json({ clientToken: data.session.clientToken });
});
Keep it server-side.YEAHCONNECT_SECRET must never appear in HTML or browser JavaScript. Amounts use cents: 4990 means S$49.90.
Request rules

amount must be an integer from 1 to 100000000, currency is currently SGD, reference is required and at most 120 characters, and Idempotency-Key must be 8 to 128 characters using letters, numbers, dots, underscores, colons or hyphens. Reuse the same key only for the same amount, reference and URLs. Accounts with more than one checkout scope may also send midCode and tidCode; otherwise the key's configured defaults are used.

Step 2 / Browser

Preload checkout, then mount it.

yeahco.js starts downloading from the document head, calls your same-origin server route and immediately loads the default payment provider with the short-lived client token. No checkout option is needed.

checkout.html
<head>
  <link rel="preconnect" href="https://yeahconnect.com" crossorigin>
  <link rel="modulepreload" href="https://yeahconnect.com/js/yeahco.js" crossorigin>
  <link rel="preload" as="style" href="https://yeahconnect.com/js/yeahco.css" crossorigin>
  <link rel="stylesheet" href="https://yeahconnect.com/js/yeahco.css" data-yeahco-style crossorigin>
</head>

<div id="yeahco-checkout"></div>

<script
  type="module"
  src="https://yeahconnect.com/js/yeahco.js"
  crossorigin="anonymous"
  data-session-url="/api/create-checkout-session"
  data-auto-mount>
</script>
Manual JavaScript

Use the manual API only when your checkout needs custom application logic.

checkout.js
import { Yeahco } from "https://yeahconnect.com/js/yeahco.js";

const response = await fetch("/api/create-checkout-session", {
  method: "POST"
});
const { clientToken } = await response.json();
await Yeahco({ clientToken })
  .mount("#yeahco-checkout");
Optional / Appearance

Style the checkout.

The SDK works without custom CSS. To match your store, style these supported elements after mounting it.

ElementWhat it controls
#yeahco-checkoutYour mount container and maximum width
.yeahco-payment-elementThe complete generated payment element
.yeahco-payment-optionsThe payment-method list
.yeahco-payment-optionOne selectable payment method
.yeahco-payment-frameThe secure hosted payment frame
.is-card-fieldsCard forms: number, expiry, CVV and Pay
.is-content-flowTaller non-card provider flows
checkout.css
#yeahco-checkout {
  width: 100%;
  max-width: 640px;
  margin-inline: auto;
}

#yeahco-checkout .yeahco-payment-element {
  display: grid;
  gap: 16px;
  color: #073d3a;
  font: 600 14px/1.45 system-ui, sans-serif;
}

#yeahco-checkout .yeahco-payment-options {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  border: 0;
}

#yeahco-checkout .yeahco-payment-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 46px;
  padding: 11px 13px;
  border: 1px solid #c7dcda;
  border-radius: 10px;
  background: #fff;
  cursor: pointer;
}

#yeahco-checkout .yeahco-payment-option:has(input:checked) {
  border-color: #0f766e;
  background: #edf9f7;
  box-shadow: 0 0 0 2px rgb(15 118 110 / 9%);
}

#yeahco-checkout .yeahco-payment-option input {
  width: 16px;
  height: 16px;
  accent-color: #0f766e;
}

#yeahco-checkout .yeahco-payment-frame {
  display: block;
  inline-size: 100%;
  max-inline-size: 100%;
  block-size: clamp(600px, 92dvh, 1040px);
  border: 1px solid #c7dcda;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 12px 30px rgb(7 61 58 / 8%);
}

@media (max-width: 650px) {
  #yeahco-checkout .yeahco-payment-frame {
    block-size: clamp(600px, 92dvh, 1040px);
  }
}
Secure fields stay hosted.You can style the iframe's size, border and shadow, but not the card number, expiry, CVV or Pay controls inside the payment provider's cross-origin iframe.
Payment confirmation

Trust the webhook.

RequiredMark an order paid only after verifying the signed Yeah Connect webhook. A browser return URL is not proof of payment.
Webhook verification

Register this route before any JSON body parser so signature verification receives the unchanged request bytes. The signature is v1= followed by an unpadded base64url HMAC-SHA256 digest.

server.js
import crypto from "node:crypto";

app.post(
  "/webhooks/yeahconnect",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const timestamp = req.get("x-yeahco-timestamp") || "";
    const signature = req.get("x-yeahco-signature") || "";
    const eventId = req.get("x-yeahco-event-id") || "";

    if (!/^\d+$/.test(timestamp)
        || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300
        || !eventId) return res.sendStatus(400);

    const expected = "v1=" + crypto
      .createHmac("sha256", process.env.YEAHCONNECT_WEBHOOK_SECRET)
      .update(`${timestamp}.${req.body.toString("utf8")}`)
      .digest("base64url");

    const valid = signature.length === expected.length
      && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
    if (!valid) return res.sendStatus(401);

    const event = JSON.parse(req.body.toString("utf8"));
    if (await hasProcessedEvent(eventId)) return res.sendStatus(200);

    const payment = event.data?.payment;
    const order = payment && await loadOrderByReference(payment.reference);
    if (!order
        || payment.amount !== order.amountInCents
        || payment.currency !== "SGD"
        || payment.status !== "succeeded") return res.sendStatus(400);

    await markOrderPaidOnce({ order, eventId, paymentId: payment.id });
    return res.sendStatus(200);
  }
);

Persist x-yeahco-event-id and the paid-order transition atomically. Return a successful response for an already processed event so retries stop safely.

Public demo

The demo uses the same auto-mount.

/demo loads the same stable module declaratively. It uses the public YEAH01 test setup, or automatically reads an optional test TID from ?tid=YOUR_TEST_TID. This test shortcut is not used for production merchant orders.

Exact /demo initialization
<div id="yeahco-checkout"></div>

<script
  type="module"
  src="/js/yeahco.js"
  data-key="YEAH01"
  data-auto-mount>
</script>