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.
<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 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".
<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:
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 loadPrefer your own markup? Fetch the product and render the price yourself:
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.
<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>| Attribute | Type | Description |
|---|---|---|
| data-hidden-email | Type: email | The 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-coupon | Type: string | A coupon code, applied at checkout as if the buyer typed it. A code that does not apply shows its reason. |
| data-hidden-variant | Type: string | The variation to preselect: its id, or its exact name. |
| data-hidden-quantity | Type: 1-100 | The quantity to preselect (never more than is in stock). |
| data-hidden-ref | Type: string | Your 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: string | Fills 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.
| Event | Detail | Sent when |
|---|---|---|
| hidden:success | Detail: orderId, status | The 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:error | Detail: code, message, status | Something stopped the purchase: checkout_failed (the order could not be created; message says why), order_expired, order_cancelled or sold_out. |
| hidden:close | Detail: completed, status, orderId | The modal closed, however it was closed. completed is true when an order was paid in it. Once per modal. |
<!-- 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
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:
// 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