Cocoon Hosted Card Fields
Take card payments on your own checkout page without card data ever touching your website or servers.
Zenoz Cocoon lets you accept card payments on your own checkout page, in your own design, without card numbers ever touching your website or your servers.
You drop a small Zenoz script on your checkout page and place four card inputs where you want them: card number, expiry, CVC and (optionally) postal code. Each input looks like part of your page, but it is actually a secure frame served by Zenoz. When your shopper types their card, the digits go straight from those frames to the Zenoz card vault. Your page only ever learns whether a field is complete or has an error, and at the end it receives a payment result.
Why merchants use Cocoon
| You want to... | Cocoon gives you... |
|---|---|
| Keep shoppers on your own site | Card fields embedded in your checkout. No redirect to a hosted payment page, no pop-up. |
| Keep your brand | Fonts, colours and focus/error styles you control. The fields blend into your form. |
| Stay out of the card-data business | Card numbers and CVCs never reach your page, your servers, your logs or your analytics, which keeps the scope of your PCI obligations as small as possible. |
| Avoid building fraud and authentication flows | Zenoz runs card-testing protection and, when the card issuer asks for it, the 3-D Secure challenge for you. |
| Fulfil orders reliably | A signed webhook tells your server when a payment succeeded or failed, even if the shopper closes the tab. |
What your shopper sees
- Your checkout page, with card fields that match the rest of your form.
- Live feedback as they type: card brand detected, an incomplete or invalid number flagged.
- If their bank requires extra verification, a short 3-D Secure step in a secure Zenoz window, then straight back to your page.
- Your own confirmation message.
Who does what
| You | Zenoz |
|---|---|
| Create a payment for the order amount on your server | Serves the card fields and the script |
| Put the script and four field placeholders on your checkout page | Collects and vaults the card data |
Call confirmPayment() when the shopper clicks Pay | Checks the card, runs fraud controls and 3-D Secure, and processes the payment |
Fulfil the order when your webhook receives payment.captured | Sends signed webhooks for every outcome |
The two keys
You create both keys yourself in Portal → Developers.
- Publishable key (
pk_test_...in sandbox,pk_live_...in production). Goes in your checkout page. It is public by design: it only works from the website domains you list on it, and only for showing card fields and completing payments your server started. It cannot read or list payments. - Secret key (
znz_...). Lives on your server only, never in a browser. Your server uses it to create each payment.
Each payment also has a short-lived client_secret (valid 30 minutes) that your server passes to the page for that one checkout.
How a payment flows
- Your server creates the customer — the name, email and address your checkout already collected — with your secret key, once per shopper. Then it creates a payment intent for the order amount with that
customer_idand gets back aclient_secret. - Your checkout page loads the Zenoz script, starts it with your publishable key and the
client_secret, and mounts the four card fields. - The shopper enters their card and clicks Pay. Your page calls
confirmPayment(). - Zenoz turns the card into a single-use token inside its own frames, completes the payment, and runs 3-D Secure if needed.
- Your server receives a
payment.captured(orpayment.failed) webhook and fulfils the order.
<script src="https://sandbox.zenoz.ai/v1/monarch-zenoz.js"></script>
<div id="card-number"></div>
<div id="card-expiry"></div>
<div id="card-cvc"></div>
<div id="postal-code"></div>
<script>
const mz = MonarchZenoz("pk_test_...", { clientSecret }); // clientSecret from your server
const fields = mz.fields();
fields.create("cardNumber").mount("#card-number");
fields.create("cardExpiry").mount("#card-expiry");
fields.create("cardCvc").mount("#card-cvc");
fields.create("postalCode").mount("#postal-code");
// on Pay: const { paymentIntent, error } = await mz.confirmPayment();
</script>
Your code, already filled inAfter you sign in, Portal → Developers → Accept cards shows a setup checklist and ready-to-copy code for your page and server, with your own publishable key and the right script address for your environment.
Getting set up
Once your merchant account is approved you receive an invitation to the Zenoz portal. From Portal → Developers:
- Add your checkout domains. Create a publishable key and list every website origin your checkout runs on, for example
https://shop.example.comandhttps://www.example.com. - Create a secret key for your server.
- Add a webhook endpoint (Portal → Webhooks) subscribed to
payment.captured, so your server knows when to fulfil. - Make a test payment in the sandbox with a test card.
- Go live by switching to your production keys and the production script address.
The checklist ticks each step off automatically as Zenoz sees it done. You can't tick it by hand, so a green checklist means your setup really works.
Test before you go live
| Environment | Script | Key prefix | Cards accepted |
|---|---|---|---|
| Sandbox | https://sandbox.zenoz.ai/v1/monarch-zenoz.js | pk_test_ | Test cards only |
| Production | https://cloud.zenoz.ai/v1/monarch-zenoz.js | pk_live_ | Real cards |
In the sandbox, use any future expiry date and any CVC:
| Card number | Result |
|---|---|
4242 4242 4242 4242 | Visa, approved |
5555 5555 5555 4444 | Mastercard, approved |
4000 0000 0000 0002 | Declined |
4000 0000 0000 9995 | Declined, insufficient funds |
4000 0000 0000 3220 | Triggers a 3-D Secure challenge |
Sandbox payments send the same webhooks as production, marked livemode: false, and never reach a card network. Real card numbers are refused in the sandbox and never stored.
Live card processingProduction card processing is being enabled account by account. Until it is switched on for yours, the Go live step in your checklist shows what it is waiting on, and a live payment attempt returns
acquirer_unavailable. You can build and test your full integration in the sandbox in the meantime.
Rules that keep card data off your systems
- Always load the script from Zenoz on every page load. Don't bundle, copy or self-host it; it is updated in place with backward-compatible fixes.
- Use Cocoon fields for every card input. Never add your own input for card number, expiry or CVC.
- Serve your checkout over HTTPS.
- Keep the secret key on your server. Only the publishable key belongs in the page.
- Fulfil from the webhook, not the browser result.
Common questions
Do I need to collect the shopper's name and email in the card fields? No. The card fields only take card details. Send the name, email and billing address you already collected from your server: create the customer once (POST /v1/merchant/customers), store its id with your own user record, and pass it as customer_id when you create each payment intent. Receipts go to the customer's email. For a guest checkout, leave customer_id out.
Do I ever see the card number? No. You see the card brand, last four digits and expiry, which is enough for receipts and your order history.
Can someone steal my publishable key? It is visible in your page source by design. It only works from the domains you listed and only against payments your server created. If you want to replace it, rotate it in the portal; the old key keeps working for 24 hours while you deploy the new one.
What if my shopper's bank asks for verification? confirmPayment() shows the 3-D Secure step in a Zenoz window and only resolves once it is done. You don't build anything for it.
What error messages can I show shoppers? Errors of type card_error (declined, incorrect CVC, expired card, and so on) carry a message that is safe to display as-is.
API reference
Updated 20 days ago