SSO embed · v1

    Partner Embed (SSO) contract

    Drop the Music Video Marketplace Workspace into your own product as an iframe, with your users already signed in. Your server signs a small JSON payload describing the logged-in user with your client_secret (HMAC-SHA256); we verify it, link or provision the matching MVM account, and boot the embed under your branding. No user-facing login step, no password sharing.

    Building your own front end instead of embedding ours? See the Partner API docs.

    At a glance

    1. Your backend builds a JSON payload for the current user.
    2. Your backend signs the raw JSON string with HMAC-SHA256 + client_secret.
    3. Your frontend loads the embed URL with the base64 payload and hex signature.
    4. We verify, create a 24-hour session, and render the Workspace with your logo + accent colour.

    Sign server-side only. Your client_secret must never reach a browser.

    Credentials

    CredentialDescriptionWhere it is used
    client_idYour unique partner identifier. Not secret.Embed URL path + request bodies
    client_secretHMAC signing key.Server-side signing only — never expose to browsers
    client_id:     pk_partner_demo_0000000000000000
    client_secret: sk_partner_demo_REPLACE_ME_0000000000000000  # example only

    Credentials are issued during onboarding (invite only — there is no public signup). Ask us to rotate immediately if a secret leaks.

    HMAC-SHA256 signing

    1. Build a JSON payload string containing the user's details.
    2. Sign the raw JSON string (not the base64 version) with HMAC-SHA256 and your client_secret.
    3. The signature is a lowercase hex string.
    const crypto = require('crypto');
    
    const payloadString = JSON.stringify({
      partner_external_user_id: user.id,
      email: user.email,
      display_name: user.name,
      timestamp: new Date().toISOString(),
    });
    
    const signature = crypto
      .createHmac('sha256', CLIENT_SECRET)   // sk_partner_demo_REPLACE_ME_...
      .update(payloadString)
      .digest('hex');
    
    const b64 = Buffer.from(payloadString).toString('base64');

    Byte-for-byte matters: sign the exact string you send. Re-serialising the JSON on the way out (different key order or spacing) invalidates the signature.

    Payload contract

    {
      "partner_external_user_id": "your-user-123",
      "email": "user@example.com",
      "display_name": "Jane Doe",
      "timestamp": "2026-03-23T12:00:00.000Z",
      "logo_url": "https://cdn.yourbrand.example/logo.svg",
      "accent_color": "#1D4ED8"
    }
    FieldRequiredDescription
    partner_external_user_idYesYour platform's unique ID for the currently logged-in user. Must be unique per user.
    emailRecommendedThe logged-in user's email — used to link existing MVM purchases and to auto-provision an MVM account.
    display_nameNoDisplay name shown in the embed.
    timestampRecommendedISO 8601. Must be within 5 minutes of server time. Without it, payloads never expire (less secure).
    logo_urlNoAbsolute https:// logo URL. Overrides the logo we have stored for you, for this session.
    accent_colorNo6-digit hex colour (e.g. #1D4ED8). Overrides the stored accent, for this session.

    Never put your partner admin email or a hardcoded user ID in the payload — every user would then share a single MVM account, and see each other's purchases.

    Timestamp freshness

    Math.abs(Date.now() - new Date(timestamp).getTime()) <= 5 * 60 * 1000

    Older than 5 minutes returns 403 Payload expired. Generate a fresh payload + signature on every iframe load — do not cache or reuse signed payloads.

    Embed URL format

    https://musicvideomarketplace.com/partner/embed/{CLIENT_ID}?payload={BASE64_PAYLOAD}&sig={HMAC_SIGNATURE}

    payload is the base64 of the signed JSON string, URL-encoded. sig is the lowercase hex HMAC of the raw JSON string.

    https://musicvideomarketplace.com/partner/embed/pk_partner_demo_0000000000000000
      ?payload=eyJwYXJ0bmVyX2V4dGVybmFsX3VzZXJfaWQiOiJ5b3VyLXVzZXItMTIzIn0%3D
      &sig=0000000000000000000000000000000000000000000000000000000000000000

    Mint the URL in a server endpoint of your own (e.g. GET /api/mvm-embed-url) and set it as the iframe src at runtime.

    <iframe
      id="mvm-embed"
      style="width:100%;height:800px;border:none;border-radius:12px;background:#111"
      allow="autoplay; fullscreen; clipboard-write"
    ></iframe>
    <script>
      fetch('/api/mvm-embed-url')                 // your endpoint, your auth
        .then((r) => r.json())
        .then(({ embedUrl }) => {
          document.getElementById('mvm-embed').src = embedUrl;
        });
    </script>

    Self-serve branding (logo_url / accent_color)

    Both branding fields are part of the signed payload, so they are covered by the same HMAC-SHA256 signature as the identity fields — they cannot be tampered with or injected by a browser without your client_secret. There is no separate signature for branding.

    Resolution order, per session:

    1. Signed payloadlogo_url / accent_color, when present and valid.
    2. Stored config — the logo URL / accent colour we have on file for your account (branding_config), set by your partner manager.
    3. Default — no partner branding.

    Each field resolves independently: sending only accent_color keeps the stored logo. Invalid values (a non-http(s) URL, malformed hex) are ignored rather than rejected — the session still boots and falls back to the stored value, so a bad colour never locks your users out.

    const payloadString = JSON.stringify({
      partner_external_user_id: user.id,
      email: user.email,
      timestamp: new Date().toISOString(),
      logo_url: "https://cdn.yourbrand.example/logo.svg",
      accent_color: "#1D4ED8",
    });
    // sign payloadString exactly as above — no separate branding signature

    The verification response echoes what was used in branding_source, which is the quickest way to confirm your fields were accepted:

    "branding_source": { "logo": "payload", "accent": "branding_config" }
    ValueMeaning
    payloadTaken from your signed payload.
    branding_configYour payload omitted it (or it was invalid) — stored config used.
    defaultNeither available — no partner branding applied.

    Verification endpoint

    The embed calls this for you. Use it directly only when you want to test a signature from your own backend.

    POST https://awfrtgkoramnvpsvrjjv.supabase.co/functions/v1/partner-verify-session
    Content-Type: application/json
    
    {
      "client_id": "pk_partner_demo_0000000000000000",
      "payload": "{\"partner_external_user_id\":\"user-123\",\"email\":\"user@example.com\",\"display_name\":\"Jane Doe\",\"timestamp\":\"2026-03-25T12:00:00.000Z\"}",
      "signature": "0000000000000000000000000000000000000000000000000000000000000000"
    }

    payload here is the raw JSON string (the exact string you signed) — base64 is only used in the iframe URL parameter.

    200 OK
    {
      "session_token": "00000000-0000-0000-0000-000000000000",
      "partner_name": "Demo Partner",
      "partner_type": "purchase",
      "logo_url": "https://cdn.yourbrand.example/logo.svg",
      "accent_color": "#1D4ED8",
      "branding_source": { "logo": "payload", "accent": "branding_config" }
    }

    Sessions last 24 hours from last activity. Subsequent calls pass session_token in the JSON body (not as a header).

    Errors

    StatusMessageFix
    401Invalid signatureSign the raw JSON string, hex digest, correct client_secret.
    403Payload expiredRegenerate payload + signature per iframe load.
    403Unknown or disabled client_idCheck the ID in the URL path; contact us if disabled.
    400Missing partner_external_user_idAlways send a per-user ID.

    Checklist before go-live

    • client_secret lives only in server-side config, never in client JS.
    • Payload carries the end user's ID and email, not your admin account.
    • timestamp included and freshly generated on every load.
    • Branding confirmed via branding_source in the verification response.
    • Iframe allows autoplay and fullscreen.