Create a session (server-side)
POST /v1/applicationSessions with your API key. Pre-fill contact and
organization data, lock fields read-only, skip steps, and set a theme — the session's
configuration is authoritative.
dhango's merchant onboarding runs inside an iframe on your page — inline or as a modal — styled to match your brand and driven by a small JavaScript SDK. Your platform pre-fills what it already knows, controls which fields and steps the applicant sees, and gets a webhook the moment onboarding finishes.
POST /v1/applicationSessions with your API key. Pre-fill contact and
organization data, lock fields read-only, skip steps, and set a theme — the session's
configuration is authoritative.
Load embed.js from the sign-up origin and call
Dhango.signup(...) with the session id — inline in a container or as a
modal overlay. The iframe sizes itself and closes itself on completion.
Typed events stream to your callbacks (steps, validation, completion — identifiers only, never applicant data). Your webhook endpoint is notified when onboarding finishes, before account activation.
<!-- Load the SDK from the sign-up origin, then: --> var signup = Dhango.signup({ container: '#signup-slot', // or mode: 'modal' sessionId: '<from POST /v1/applicationSessions>', config: { theme: { primaryColor: '#0057ff', fontFamily: 'Poppins' } }, onComplete: (e) => showThankYou(e.sessionId), });
<!-- Sandbox --> <script src="https://signup.dhangodemo.com/embed.min.js"></script> <!-- Production --> <script src="https://signup.dhango.com/embed.min.js"></script>
Dhango.signup(...) needs no url option at all. A readable,
unminified build is served at /embed.js for integration and debugging.
Don't self-host or bundle a copy: the SDK is evergreen, and hosts can feature-detect
via the capabilities array on the dhango:ready event.
The sign-up flow only allows framing from origins your platform has registered —
they become the CSP frame-ancestors allowlist on the sign-up host.
Until at least one origin is registered, browsers refuse to render the iframe
anywhere.
In the dhango dashboard, open Accounts → your platform account → the API Credentials tab, and scroll to the Embedded Sign Up Origins panel (just below Webhook Integration). You'll need a dashboard user with permission to manage the platform's API credentials.
Click Edit, add each origin, and save. Changes reach the sign-up host within about five minutes — no deploy on either side. Removing every origin locks framing down again.
http://localhost /
http://127.0.0.1 for local development. Up to 10 origins per platform.
A wildcard subdomain entry like https://*.partner.com matches
subdomains at any depth — but the * is only valid as the entire leading
label (https://*partner.com is rejected), and it never matches the apex:
list https://partner.com itself separately if you embed there. Scheme and
port must still match exactly. The panel validates entries as you add them and
normalizes (lowercases, deduplicates) the list on save.
In the dhango dashboard — Accounts → your platform account →
API Credentials tab → Webhook Integration panel — set your
platform's webhook URL: a single HTTPS endpoint that receives all
webhook events for your platform. There are no per-event subscriptions;
dispatch on the payload's Type.
Generate the HMAC key from the same panel. It's created server-side (never chosen by you) and stays retrievable there for authorized dashboard users — store it with your other secrets. Every delivery is signed with it so you can prove the POST came from dhango.
Each POST carries AccountKey, Timestamp, and
HMACSignature headers. Recompute the signature over the raw body,
check freshness, then handle the event. Onboarding-finished is
Type: "ApplicationSubmitted".
// Node/Express example — verify HMAC-SHA256 over "{Timestamp}.{rawBody}" (base64) const crypto = require('crypto'); app.post('/webhooks/dhango', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.header('HMACSignature') || ''; const timestamp = req.header('Timestamp') || ''; const expected = crypto .createHmac('sha256', process.env.DHANGO_WEBHOOK_SECRET) .update(`${timestamp}.${req.body}`) // sign the timestamp + RAW body, dot-joined .digest(); // raw bytes; header carries base64 // HMAC-SHA256 in base64 is exactly 44 chars. Enforce canonical base64 by regex // BEFORE decoding — Buffer.from(sig, 'base64') silently skips invalid characters, // so a mangled header could otherwise decode to the same bytes and pass. const canonical = /^[A-Za-z0-9+\/]{43}=$/.test(signature); const provided = Buffer.from(signature, 'base64'); const authentic = canonical && provided.length === expected.length && crypto.timingSafeEqual(provided, expected); const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // bounds the replay window if (!authentic || !fresh) return res.status(401).end(); const event = JSON.parse(req.body); // { Type, Data } — PascalCase, string enum if (event.Type === 'ApplicationSubmitted') { // onboarding finished // event.Data: { ApplicationId, PlatformIdentifier, OrganizationName, // DoingBusinessAs, ContactEmailAddress, SubmitDate } // Deliveries can repeat (retries, replays inside the freshness window): // make this idempotent — e.g. upsert keyed on (Type, ApplicationId). markMerchantOnboarded(event.Data.PlatformIdentifier, event.Data.ApplicationId); } res.status(200).end(); });
Data.PlatformIdentifier echoes the identifier you set when creating the
session — use it to correlate the webhook back to your own merchant record.
Background, card, text, and accent colors, border radius, font size, and any Google Fonts family — applied inside the iframe so the flow blends into your site.
Send what you already know on the session — contact, organization, tax id, entity type — and mark fields read-only. Locked values are enforced server-side.
Waive steps your platform handles elsewhere. A session with a complete contact can even skip straight past step one.
Hide the flow's own chrome — stepper, Next/Back, language menu, close ✕ — and drive
it from your page with promise-returning SDK commands (next(),
back(), getState()). With autoClose: false
the iframe stays up until you call destroy().
English, Spanish, German, and French. Pass the language in with the embed or switch
it live with setLanguage().
A versioned, bidirectional postMessage protocol with strict origin checks, plus HMAC-signed webhooks for onboarding-finished and account activation.
frame-ancestors from a runtime allowlist); every message crosses the
boundary with a strict origin check and carries identifiers only — never applicant data.
Read-only and skip-step configuration set on the session is enforced by the API, not just
the UI.