Onboarding a New Merchant
This API surface is for integration partners building on top of the Rokt network. Rokt ecommerce partners integrating placements on their own checkout should use the Rokt Ecommerce developer docs instead.
This recipe walks the canonical path: from a brand-new merchant in your platform to a live, revenue-generating Rokt account. Every step is callable from your backend with an API token bearer token, the X-Platform-Parent-Account-Id header, and an Idempotency-Key.
Pre-reqs: you've read Overview, Authentication, and Quickstart. You have a valid API token and your platform_parent_account_id. Every example assumes $PARENT holds that value.
- Register the merchant
POST
/v1/accounts/register/partnershipwith the merchant's brand, partner-taxonomy IDs, country, store URL, and your stableexternal_account_id. Capture the returnedaccount_idfromdata.account_id— every subsequent call keys off it.Pass the optional
pagesarray to declare which surfaces this merchant will render placements on. Each entry maps a partner-facing surface (confirmation,tracking,returns) to a layout style (OverlayorEmbedded). See Pages and Layouts for the full surface vocabulary.- curl
- python
curl -X POST https://accounts.rokt.com/v1/accounts/register/partnership \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-d '{
"brand": "Acme Apparel",
"vertical_id": 1500,
"sub_vertical_id": 1610,
"country_code": "US",
"platform_parent_account_id": "<your-platform-parent-account-id>",
"store_identifier": "https://acme-apparel.example.com",
"external_account_id": "partner-merchant-abc123",
"pages": [
{ "surface": "confirmation", "layout_type": "Overlay" },
{ "surface": "tracking", "layout_type": "Embedded" }
]
}'import requests
r = requests.post(
"https://accounts.rokt.com/v1/accounts/register/partnership",
headers={
"Authorization": f"Bearer {token}",
"X-Platform-Parent-Account-Id": parent_account_id,
},
json={
"brand": "Acme Apparel",
"vertical_id": 1500,
"sub_vertical_id": 1610,
"country_code": "US",
"platform_parent_account_id": "<your-platform-parent-account-id>",
"store_identifier": "https://acme-apparel.example.com",
"external_account_id": "partner-merchant-abc123",
"pages": [
{"surface": "confirmation", "layout_type": "Overlay"},
{"surface": "tracking", "layout_type": "Embedded"},
],
},
)
body = r.json()["data"]
account_id = body["account_id"]
pages = body.get("pages", [])
# Store each entry's page_identifier per surface — you'll pass these into
# the Web SDK's selectPlacements call later.Response:
{
"status": 200,
"error": null,
"message": "ok",
"request_id": "0e3a1b9c-...",
"data": {
"account_id": "<your-account-id>",
"pages": [
{ "surface": "confirmation", "page_id": "bfb5b9be-...", "layout_id": "9d11d8aa-...", "page_identifier": "confirmation_page" },
{ "surface": "tracking", "page_id": "2b9d8a5e-...", "layout_id": "f8700369-...", "page_identifier": "tracking_page" }
]
}
}注記Registration is idempotent on
external_account_id. Re-POSTing with the sameexternal_account_idreturns the sameaccount_id— no duplicate account is created. Use this to make your onboarding retry-safe.警告store_identifiermust be a valid URL between 3–400 chars and include the scheme. Sendhttps://acme.myshopify.com, notacme.myshopify.com; the bare hostname returns a 400. - Inspect default controls
Your partnership preset seeds default marketplace controls when the account is created. Read them before customizing so you know your baseline.
curl https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT"The MCL response payload (
data.translatedVerticals) includes a list showing your partner taxonomy IDs alongside the Rokt internal IDs they map to — useful for surfacing readable category names in your UI. - Customize marketplace controls (optional)
PUT the full desired blocklist. This is SET semantics: send the complete list every time — anything you omit becomes unblocked. See SET Semantics and the Vertical Taxonomy concept page.
curl -X PUT https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/marketplacecontrolslists \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Acme Network Controls",
"blockedVerticals": [
{ "partnerVerticalId": 1500, "partnerSubVerticalId": 1610, "policy": "Block", "position1Policy": "Block" },
{ "partnerVerticalId": 1500, "partnerSubVerticalId": 1611, "policy": "Block", "position1Policy": "Block" }
],
"domains": [
{ "domain": "competitor.example.com", "policy": "Block" }
],
"contentHash": null
}'ヒントcontentHash: nullis fine on first write. For subsequent updates, pass the hash from the previous GET to opt in to optimistic-concurrency checks. The end-to-end stale-hash rejection is preview-grade — verified by acceptance but not yet observed live on the partner surface. See Updating Controls. - Start payout setup
POST
/v1/partnership/accounts/{account_id}/payout-setupto initiate Stripe Connect onboarding. Pick one of two shapes:experience: "embedded"— render Stripe's onboarding UI inside your app. Do not includecontact; server returns 400 if you do.experience: "hosted_invite"— Rokt emails the merchant a link.contact.emailis required.
# Embedded — no contact block
curl -X POST https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/payout-setup \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"provider": "stripe_connect",
"experience": "embedded"
}'
# Hosted-invite — contact required
curl -X POST https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/payout-setup \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"provider": "stripe_connect",
"experience": "hosted_invite",
"contact": { "email": "merchant@example.com", "name": "Acme Apparel" }
}'The response carries one of two sub-shapes inside
data:- For
embedded:data.embedded.publishable_key+data.embedded.client_secret— wire these into Stripe.js to render the flow. - For
hosted_invite:data.hosted_invite.invite_id+data.hosted_invite.email— the merchant gets the email automatically.
警告Never put bank account, card, routing, SSN, IBAN, tax, PayPal, or external-account fields anywhere in the request — these field names are forbidden at any nesting level and return 400. Stripe collects those directly from the merchant.
- Wait for payout completion
Poll GET
/v1/partnership/accounts/{account_id}/payout-setup/statusuntildata.payouts_enabled === trueanddata.details_submitted === true. Recommended cadence: every 30 seconds, give up after 7 days. Stripe onboarding is partner-driven and can take time — don't tight-loop.curl https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/payout-setup/status \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT"{
"status": 200,
"error": null,
"message": "ok",
"request_id": "...",
"data": {
"provider": "stripe_connect",
"setup_id": "stp_...",
"managed_account_id": "<your-account-id>",
"manager_account_id": "<your-platform-parent-account-id>",
"connected_account_id": "acct_...",
"details_submitted": false,
"payouts_enabled": false,
"charges_enabled": false,
"requirements_currently_due_count": 3,
"requirements_eventually_due_count": 0,
"requirements_past_due_count": 0,
"disabled_reason": null,
"updated_at": "2026-05-20T19:14:00Z"
}
}If
disabled_reasonis non-null orrequirements_past_due_count > 0, surface that back to the merchant so they can finish.If your 7-day polling window expires without
payouts_enabled === true, don't leave the merchant stuck. Recover with this sequence:- Re-invoke
POST /v1/partnership/accounts/{account_id}/payout-setupto generate a fresh onboarding session. The same merchant can have multiple sessions outstanding — only the latest one matters for completion, so olderembeddedclient secrets orhosted_invitelinks can be safely abandoned. Use a newIdempotency-Keyfor the new session. - Surface the underlying
disabled_reason(from the most recent polling response'sdata.disabled_reason) back to the merchant verbatim so they know exactly what to fix on the Stripe side before they restart — e.g. missing tax ID, failed identity verification, unverified bank account. The Stripe-defined reason strings are stable enough to drive an in-product help message. - If
disabled_reasonindicates a Stripe-side issue you can't surface or remediate (e.g.requires_rokt_review,under_review, or a value not documented in Stripe's Connect onboarding docs), file a support ticket. Include theX-Operation-Idfrom the original payout-setup response and the merchant'saccount_id— that's enough for Rokt operators to pull the Stripe connected-account record and unstick it.
- Re-invoke
- Activate the partnership
Once payouts are complete and you're happy with controls, flip status to
active. The cascade enables every non-archived page variant on the account.curl -X PUT https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/status \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "status": "active" }' - Verify the cascade
Round-trip the status read to confirm every variant flipped.
curl https://accounts.rokt.com/v1/partnership/accounts/<your-account-id>/status \
-H "Authorization: Bearer $TOKEN" \
-H "X-Platform-Parent-Account-Id: $PARENT"data.statusshould be"active"and every entry indata.variants[]should show"active". If you see"mixed", the cascade missed some variants — file a support ticket with yourIdempotency-Keyand therequest_idfrom the response envelope. - Wire the Web SDK on the merchant's pages
The orchestrator has provisioned the account, pages, and layouts. The remaining step happens on the merchant's site: load the Rokt Web SDK once per page and call
selectPlacementson each surface, passing the matchingpage_identifierfrom the registration response.<script type="module">
await window.RoktLauncherScriptPromise;
const launcher = await window.Rokt.createLauncher({
accountId: "<account_id from registration>",
sandbox: true
});
// Confirmation page:
await launcher.selectPlacements({
identifier: "confirmation_page", // ← from registration response's pages[].page_identifier
attributes: { email, firstname, lastname, confirmationref, amount, currency, country }
});
</script>Embedded surfaces (
Embeddedlayout type) need an anchor element on the page. By default the SDK looks for<div id="rokt-container"></div>. See SDK Integration for the full setup including the loader snippet, attribute coverage, and SPA notes.
Customizing the merchant's experienceCustomizing the merchant's experience への直接リンク
Registration ships every merchant with the partnership preset's default theme and surface set. Once the integration is live, two endpoints let you evolve that baseline without re-registering: the 5-token layout PATCH for visual theming, and the add-page POST for adding new surfaces (e.g. tracking after the merchant initially launched with confirmation only). Both are partner-callable, idempotent, and scoped to the same managed account — no Rokt-side intervention needed.
Theme a merchant's layout via the 5-token PATCH (background, primary, secondary, font, border-radius).
Add a new surface (confirmation, tracking, returns) to a live merchant without re-registering.
The merchant is live. Next: