Skip to content

Developer docs

Buy button and live price

Include one script and give any element your store and product: clicking it opens your checkout on your own page. A price span keeps itself current, and your page gets an event for every order.

Buy button

Try it: buy the demo product

This is the live Buy button of the demo store. The checkout opens on this page.

html
<button data-hidden-store="my-store" data-hidden-product="lifetime-license">Buy now</button>
<script src="https://sellhidden.com/embed.js" async></script>

Give any element data-hidden-store and data-hidden-product, include embed.js once, and clicking opens a checkout modal with your live product page. The buyer never leaves the host page; the modal closes with its X, Escape, a click outside, or from checkout once the order is placed.

button.htmlhtml
<button data-hidden-store="my-store" data-hidden-product="lifetime-license">Buy now</button>
<script src="https://sellhidden.com/embed.js" async></script>

The script sets no cookies, reads nothing on the host page and takes the checkout host from its own src, so a copied snippet cannot be redirected. Messages from the modal are accepted only from that origin. The v1 snippet (<script data-store data-product>) keeps working and now opens the modal too.

Live price

A price span keeps itself current from the public API (cached 60 seconds). Optional attributes: data-hidden-price-prefix, data-hidden-currency-symbol and data-hidden-format="raw". Once loaded the span gets data-hidden-in-stock="true|false".

price.htmlhtml
<span data-hidden-price="my-store/lifetime-license" data-hidden-price-prefix="from "></span>

JavaScript API

Open and close the modal from your own code, or rebind after rendering new elements in a single-page app:

app.jsjs
window.HiddenEmbed.open("my-store", "lifetime-license"); // open the checkout modal
window.HiddenEmbed.open("my-store", "lifetime-license", {  // same, prefilled
  email: "buyer@example.com", variant: "Team", quantity: 2, ref: "user_42", meta: { discord_name: "neo" },
});
window.HiddenEmbed.close();                                // close it
window.HiddenEmbed.scan();                                 // bind elements added after load

Prefer your own markup? Fetch the product and render the price yourself:

custom.jsjs
const res = await fetch("https://api.sellhidden.com/v1/public/products/my-store/lifetime-license");
const { ok, product } = await res.json();
if (ok) priceEl.textContent = `$${product.displayPriceUsd.toFixed(2)}`;

Prefill

Fill in what you already know about the buyer. The attributes are read when the button is clicked, so you can set them after your page loads (for example once the visitor signs in). The buyer can still change everything, and the checkout checks every value again.

prefilled.htmlhtml
<button data-hidden-store="my-store" data-hidden-product="lifetime-license"
  data-hidden-email="buyer@example.com"
  data-hidden-coupon="SPRING"
  data-hidden-variant="Team"
  data-hidden-quantity="2"
  data-hidden-ref="user_42"
  data-hidden-meta-discord_name="neo">Buy now</button>
Embed prefill attributes
AttributeTypeDescription
data-hidden-emailType: emailThe buyer's email, where the order link and receipt go. Filled in only when the script tag carries a valid site key for your page and the buyer's browser has no remembered email; otherwise it is offered as a suggestion the buyer accepts.
data-hidden-couponType: stringA coupon code, applied at checkout as if the buyer typed it. A code that does not apply shows its reason.
data-hidden-variantType: stringThe variation to preselect: its id, or its exact name.
data-hidden-quantityType: 1-100The quantity to preselect (never more than is in stock).
data-hidden-refType: stringYour own reference, up to 64 of A-Z a-z 0-9 _ . : @ + / = -. Saved on the order as customFields.ref (webhooks, API, dashboard) and returned in every embed event.
data-hidden-meta-<key>Type: stringFills your checkout field with that key (lowercase letters, digits and _). data-hidden-meta-discord_user_id fills the Discord user ID of Discord products (with a valid site key; otherwise it is offered as a suggestion).

The same values work on a direct link as query parameters: /p/lifetime-license?buy=1&email=buyer@example.com&coupon=SPRING&meta.discord_name=neo.

Checkout events

embed.js tells your page what happens in the modal. Listen for the hidden:success, hidden:error and hidden:close events on window (works even before the script loads), or register callbacks with HiddenEmbed.on("success", fn) once it has. Every detail carries store, product and your ref.

Embed events
EventDetailSent when
hidden:successDetail: orderId, statusThe order is paid. status is paid, delivered, oversold or failed (paid, delivery needs attention). Once per order. orderId is null unless your page holds a site key.
hidden:errorDetail: code, message, statusSomething stopped the purchase: checkout_failed (the order could not be created; message says why), order_expired, order_cancelled or sold_out.
hidden:closeDetail: completed, status, orderIdThe modal closed, however it was closed. completed is true when an order was paid in it. Once per modal.
events.htmlhtml
<!-- Listeners work before or after embed.js loads -->
<script>
  window.addEventListener("hidden:success", (e) => {
    const { orderId, status, store, product, ref } = e.detail;
    // status: paid | delivered | oversold | failed. orderId is null without a site key.
    showThankYou(ref);
  });
  window.addEventListener("hidden:error", (e) => {
    console.warn(e.detail.code, e.detail.message); // checkout_failed, order_expired, ...
  });
  window.addEventListener("hidden:close", (e) => {
    if (!e.detail.completed) offerHelp(); // closed before paying
  });
  // After embed.js loaded, the same as callbacks:
  // window.HiddenEmbed.on("success", (detail) => { ... });
</script>
<script src="https://sellhidden.com/embed.js" data-hidden-key="hsk_your_site_key" async></script>

The order id needs a site key

An order id opens the order page and its delivered items, so the modal only hands it to a site you named: create a site key for your website address under Developers, Embeds and add it to the script tag as data-hidden-key. The modal learns your page's origin from the browser, checks the key against it and then posts to that exact origin only. On any other page orderId is null. After removing a team member or giving up a domain, the store owner can revoke every site key at once in the same place; your sites keep selling and get the order id again once they have a new key. Treat the event as a signal for your interface; fulfil from the signed order.paid webhook (match it with customFields.ref).

Your own iframe

Framing the checkout yourself instead of using embed.js? Answer the modal's hidden:ready message with hidden:hello and you receive the same messages:

own-iframe.jsjs
// Your own <iframe src=".../p/lifetime-license?embed=1"> instead of embed.js:
window.addEventListener("message", (e) => {
  if (e.origin !== "https://my-store.sellhidden.com") return;
  const msg = e.data;
  if (msg && msg.type === "hidden:ready") {
    // Answer every page load in the frame; the browser tells it your origin.
    e.source.postMessage({ type: "hidden:hello", v: 2, key: "hsk_your_site_key" }, e.origin);
  }
  if (msg && msg.type === "hidden:success") console.log(msg.status, msg.orderId);
  if (msg && msg.type === "hidden:close") iframe.remove();
});

Get help

Send us the endpoint, the request or delivery id and the response you got. Security findings go through responsible disclosure. Live platform status is on the support page.

See also