Take your customers' payments on your own checkout: the embed asks your page to take the payment, your server confirms the paid order, the video unlocks, and we bill you the wholesale price.
First published version: the optional "partner takes the payment" model, covering setup, the postMessage contract, the signed server confirmation, prices, testing, billing and a go-live checklist.
Your customers pay you, on your site, your way. You tell us each order is paid, the video unlocks inside the embed, and we invoice you a fixed wholesale price per sale. You keep the difference.
This is optional and per partner. Partners who keep the default, where customers pay us inside the embed, are unaffected. Already live with us taking the payment? You can test first and move over in your own time (section 3). Signing your users in works exactly as in the embed guide, so read that first.
The customer pays you on your own checkout, at the price you choose.
Your server sends us one signed call per paid order. The video unlocks straight away.
Each live sale adds your wholesale price to a running total, charged to your saved card at the end of each billing period.
We use a single standard price for every video in the catalogue, and we recommend you do the same: choose one price and use it for everything instead of pricing videos one by one. It is simpler for your customers, simpler for you, and it avoids mistakes. The amount is entirely up to you. See section 8 for how to set it.
Just keep it above your wholesale price (shown on your dashboard's Billing tab as Per purchase). We bill that amount on every live sale whatever your customer paid, so a sale priced below it loses you money. We do not block it, we only flag it in your ledger.
MVM_PURCHASE_REQUEST message. Your page opens your checkout as an overlay or popup.partner-confirm-purchase call for that order.MVM_PURCHASE_COMPLETE to the embed, which shows the video as owned.Hard rule: never reload or navigate the page that holds the embed. The customer may have an unsaved edit inside it. Use an overlay or popup for your checkout.
Say your wholesale price is £99.00 per video, your billing period is 14 days, and you choose to sell each video for £149.00.
Day 2 Ana buys "Sunset Vibes" pays you £149.00 you confirm -> we add £99.00 to your running total
Day 5 Ben buys "Golden Dusk" pays you £149.00 you confirm -> running total £198.00
Day 9 Ana buys "Neon Rain" pays you £149.00 you confirm -> running total £297.00
Day 14 The period ends we invoice £297.00 and charge your saved card
Customers paid you £447.00, you owe us £297.00, and you keep £150.00 (£50.00 per sale).If you set an early charge threshold of £250.00, we would not wait for day 14: the running total passes £250.00 on day 9, so we would invoice £297.00 then and start a new total after it.
If you had priced a video at £79.00 instead, you would still owe us the same £99.00 for it, and that sale would be flagged below_floor in your ledger.
If that card payment fails, your account shows payment failed and you can keep selling through the grace period. Pay the invoice or update your card from the Billing tab at any time; after the grace period, new live sales are refused until it is settled. Customers always keep what they bought.
Sales made with your test credentials are never added to the total. Only live sales are billed.
Already live with customers paying us inside the embed? You do not have to change everything at once. We move you over in two waves, and the second one is yours to trigger.
Choosing your moment.
We set, in our admin: that partner payments are enabled for you, your wholesale price per video, billing currency, billing period, and optionally a default retail price and an early charge threshold (section 11).
You do, in your partner dashboard:
https://www.yoursite.com, with no page path. A page that is not listed never receives the Buy message.Messages use window.postMessage. Only accept events whose origin is exactly https://musicvideomarketplace.com and whose source is your iframe's contentWindow, and always send replies with the target origin https://musicvideomarketplace.com.
From the embed, when the customer clicks Buy:
{ "type": "MVM_PURCHASE_REQUEST",
"requestId": "2f0c7c1e-9a2b-4c3d-8e4f-5a6b7c8d9e0f",
"videoId": "6a1f1b2e-0c2d-4e5f-8a9b-0c1d2e3f4a5b",
"title": "Sunset Vibes",
"price": 129.00, // your display price, or null: show your own
"currency": "GBP",
"user": { "externalUserId": "user_8841", "email": "customer@example.co.uk" },
"isTest": false }From your page, back to the embed (same requestId):
{ "type": "MVM_PURCHASE_COMPLETE", "requestId": "…" } // only after your server's confirmation returned success
{ "type": "MVM_PURCHASE_CANCELLED", "requestId": "…" } // the customer closed your checkoutMVM_PURCHASE_COMPLETE only makes the embed check straight away: the video is unlocked by your server's confirmation, never by a browser message.
window.addEventListener('message', async (event) => {
const iframe = document.getElementById('mvm-embed');
if (event.origin !== 'https://musicvideomarketplace.com' || event.source !== iframe.contentWindow) return;
const msg = event.data;
if (!msg || msg.type !== 'MVM_PURCHASE_REQUEST') return;
const paid = await openYourCheckout({ title: msg.title, price: msg.price, currency: msg.currency });
if (!paid) {
iframe.contentWindow.postMessage({ type: 'MVM_PURCHASE_CANCELLED', requestId: msg.requestId }, 'https://musicvideomarketplace.com');
return;
}
const res = await fetch('/api/mvm-confirm-purchase', { // your own server endpoint (section 7)
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ requestId: msg.requestId, videoId: msg.videoId, amount: paid.amount, currency: paid.currency }),
});
if (res.ok) iframe.contentWindow.postMessage({ type: 'MVM_PURCHASE_COMPLETE', requestId: msg.requestId }, 'https://musicvideomarketplace.com');
});Your server endpoint must take the customer's id and email from your own signed-in session, not from the browser's request body, and use the same partner_external_user_id you signed into the embed link.
POST https://musicvideomarketplace.com/api/public/partner-fn/partner-confirm-purchase
| Header | Value |
|---|---|
Content-Type | application/json |
x-mvm-client-id | Your client ID (your test client ID in test mode) |
x-mvm-timestamp | ISO-8601 time, within ±5 minutes of ours |
x-mvm-signature | Lowercase hex HMAC-SHA256 of `${timestamp}.${rawBody}` with your client secret |
Sign the body exactly as you send it: build the JSON string once and send that same string.
| Body field | Required | Meaning |
|---|---|---|
partner_order_ref | Yes | Your order number. Unique per partner. A repeat returns the same sale, so retrying is always safe |
video_id | Yes | The video from the purchase request |
partner_external_user_id | Yes | The customer id you signed into the embed link |
email | No | Customer's email (contact detail only) |
amount, currency | No | What the customer paid you, e.g. 129.00 and GBP |
paid_at | No | ISO-8601 payment time |
request_id | Recommended | The requestId from the Buy message: lets the embed resume after a reload |
import crypto from 'node:crypto';
export async function confirmPurchase(order) {
const body = JSON.stringify({
partner_order_ref: order.id, video_id: order.videoId, partner_external_user_id: order.userId,
email: order.email, amount: order.amountPaid, currency: order.currency,
paid_at: new Date().toISOString(), request_id: order.requestId,
});
const timestamp = new Date().toISOString();
const signature = crypto.createHmac('sha256', process.env.MVM_CLIENT_SECRET)
.update(timestamp + '.' + body).digest('hex');
const res = await fetch('https://musicvideomarketplace.com/api/public/partner-fn/partner-confirm-purchase', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-mvm-client-id': process.env.MVM_CLIENT_ID,
'x-mvm-timestamp': timestamp, 'x-mvm-signature': signature },
body,
});
const data = await res.json();
if (res.ok || (res.status === 409 && data.error === 'already_owned')) return data;
throw new Error(res.status + ' ' + data.error + ': ' + data.message);
}A successful answer looks like this (abridged):
{ "success": true, "transaction_id": "7c9e6679-…", "status": "pending",
"partner_order_ref": "ORD-2026-000123", "video_id": "6a1f1b2e-…",
"wholesale_amount": 10.00, "currency": "GBP", "customer_amount": 25.00,
"is_test": false, "idempotent": false, "flag": null,
"message": "Purchase confirmed." }A 200 answer includes wholesale_amount (what we will bill you for this sale) and is_test. Errors come as { "error": "<code>", "message": "…" }:
| Status | error | What to do |
|---|---|---|
| 401 | missing_signature, stale_timestamp, invalid_client_id, invalid_signature | Check the secret, that you signed the exact body, and your server clock |
| 402 | billing_required | Add a card on the dashboard's Billing tab. Test calls are not affected |
| 402 | billing_suspended | A charge failed and the grace period passed. Pay the open invoice or update the card |
| 403 | partner_inactive, wrong_payment_model | Your account is not active, or is not set to "The partner takes the payment". Contact us |
| 404 | request_not_found | The request_id is not yours |
| 409 | already_owned | The customer already owns it. Not billed. Treat as success, and refund them if you charged them again |
| 409 | video_unavailable | Sold out, hidden or not yet released. Not billed. Refund your customer |
| 409 | request_mismatch | The request belongs to a different video or customer, or you mixed test and live credentials |
| 5xx | internal_error | Retry with the same partner_order_ref: you can never create two billable sales for one order |
Two prices matter, and it helps to keep them apart. Your wholesale price is what we bill you per sale (after any discount we have agreed). Your selling price is what your customer pays you. We take no percentage of it and never see your checkout.
Whatever you set below is shown on the Buy button inside the embed and sent to your page as price. It never charges anyone. The amount your customer is actually charged is whatever your own checkout asks for, so make sure the two match.
| Option | How | Best for |
|---|---|---|
| One standard price for every video (recommended) | On your dashboard, open Billing then Your prices, enter your standard price and save. It applies to every video, with nothing to send from your server. | Almost everyone. One number, changed in one place. |
| Exceptions for particular videos | Add prices to the signed embed payload (below). An exception wins over your standard price for that video. | A launch offer or a bundle on a few videos. |
| No price in the embed | Leave Your prices empty and send no prices. The message's price is null and your page shows its own price. | When your checkout already shows the price clearly. |
Add a prices object to the payload you sign for the embed link. Keys are our video ids, values are amounts in your billing currency:
{ "partner_external_user_id": "user_8841", "email": "customer@example.co.uk", "timestamp": "2026-10-28T10:00:00.000Z",
"prices": { "6a1f1b2e-0c2d-4e5f-8a9b-0c1d2e3f4a5b": 149.00 } }"flag": "below_floor" in your ledger. You simply make a loss on it.amount and currency for your own records.There are no refunds or voids on our side: a confirmed live sale is always billable. Decide before you confirm.
MVM_PURCHASE_CANCELLED instead.video_unavailable or already_owned, nothing is billed. Refund the customer if you charged them.5xx), retry the same call with the same partner_order_ref. It is safe: you can never create two billable sales for one order. Keep retrying from a queue, not only from the customer's browser.requestId it has already completed.video_unavailable exists.request_mismatch), so keep the embed link and the confirmation on the same mode.The How to embed tab ticks off each step of your test run as we see it.
| Setting (set by us) | What it means |
|---|---|
| Wholesale price per video | What we charge you for each confirmed live sale, less any partner discount. Also your price floor |
| Billing currency | The currency of the wholesale price and your invoices |
| Default retail price | Optional price shown to your customers in the embed unless your page sends its own |
| Billing period (days) | How often you are billed. Sales in a period are added up and charged in one invoice when it ends |
| Grace days after a failed charge | How long you may keep selling after a failed charge before live sales are refused. Paying or updating your card lifts it. Customers keep what they bought |
| Early charge threshold | Optional safety limit: if the amount owed reaches this before the period ends, we bill straight away |
402 billing_suspended until the invoice is settled.partner-confirm-purchase from your server with a stable partner_order_ref.MVM_PURCHASE_COMPLETE only after that call succeeded, and MVM_PURCHASE_CANCELLED if the customer backs out.video_unavailable.