Skip to main content
Work through this list in your test environment first, then once more with live keys before you open the flow to customers.

Keys and environment

  • Your page uses your pk_live_ publishable key, and your server uses your dk_live_ secret key. They come as a pair.
  • The secret key is in your server’s environment only. Search your built frontend for dk_live_, and expect nothing.
  • Branding is set in your production Partner Portal account. Sandbox and production are separate accounts.
  • Your CSP allows script-src https://js.doola.com and frame-src https://sdk.doola.com, and the doola-js Trusted Types policy if you enforce Trusted Types.

Sessions

  • The session route reads the customer from your own sign-in, never from the request body.
  • It answers 401 only when nobody is signed in, and turns doola’s 401 into a 502. @doola/js/server does both.
  • It accepts POST only, applies your CSRF protection, and sends Cache-Control: no-store.
  • It sends externalCustomerId, so a customer who changes their email with you stays the same customer.
  • It answers doola’s 409 with { code }, and fetchAccessToken rejects with that code. @doola/js/server does the first.
  • You log the doola error code from failed sessions on your server.

Payment

  • onFormed opens your checkout, and opening it twice for the same company creates one order.
  • Before charging, your server checks that the company belongs to the signed-in customer and is still AWAITING_PAYMENT. CANCELLED, FAILED and unknown statuses are refused, never shown as paid.
  • Your server prices the formation. Nothing about the price comes from the browser.
  • You keep one order per companyId, and each charge attempt has its own idempotency key, so a retry after a declined card still goes through.
  • You confirm payment only after the charge succeeds, and retry the confirmation on a network error or a 5xx.
  • 409 E_COMPANY_CANCELLED refunds the founder.
  • After confirming, you replace the SDK element with a new doola.create(), so the founder sees their company and the old payment screen is gone.
  • A scheduled sweep confirms charged orders that were never confirmed.

After payment

  • Your production webhook URL and signing secret are set in the Partner Portal, and your endpoint verifies signatures.
  • You handle company_formation_submitted, company_formation_completed, company_formation_failed and company_ein_issued.
  • You bring the founder back to the SDK on signature_ss4_reminder_due and signature_form8821_reminder_due.
  • You handle company_name_options_required. The founder can send new names in the SDK, and your answer through the Required actions API replaces theirs.
  • doola has every domain that hosts the SDK, so signing renders inside it.

On the page

  • Your page shows its own fallback when loadDoola() rejects, and when onLoadError reports render_error.
  • partner_session_expired sends the user to your sign-in.
  • email_in_use gives the founder a way forward, such as a different email or your support.
  • external_id_conflict alerts your team, since only a fix to your user id mapping resolves it, and customer_revoked offers your support.
  • You log mint_failed and alert when your session route keeps failing. The iframe offers the founder Try again itself.
  • You call doola.destroy() when your user signs out, and never on an ordinary route change.
  • You keep one SDK instance per page, so callbacks reach the screen that is mounted now.
  • You tested on Chrome, Firefox, Safari and iOS Safari, and on a phone-sized screen.

In production

  • You never create test formations with live keys. Every confirmed live formation is filed with the state.
  • Your team knows how to reach doola to cancel an unpaid formation.
Last modified on October 8, 2026