Connect Stripe in white label
Let your clients connect their own Stripe account to a tax ID from your product. Register your return domains, start the authorization, send the person to Stripe and read the result when they come back.
If you build a product on top of Factuarea, your clients should not have to leave it to connect their Stripe account. This flow keeps them in your portal: you start the connection, the person finishes the onboarding on Stripe's own page, and when they come back Factuarea completes the connection and sends the browser to your site.
Factuarea creates a Standard Stripe account for the tax ID and keeps it with the authorization. The account that ends up connected is always that one: nothing in the return address can change it. Once connected, it behaves like any other connected account, so auto-invoicing stays off until you turn it on.
Live only. This flow does not run in the test environment. A fact_test_ key is rejected, also on the test accounts your own account provisions. A test key cannot even carry the stripe_autoinvoicing scopes, so the call answers insufficient_scope (or profile_not_found if the tax ID is not reachable from the sandbox), and a key that carried them anyway gets module_not_available_in_sandbox. Nothing reaches Stripe and nothing is written. Use a fact_live_ key from the first call.
What you need
- A live key with
stripe_autoinvoicing:write(andstripe_autoinvoicing:readto list). No new scope is involved. - The tax ID must be one of your own account, or belong to an account you manage with
operateaccess. An account that only received access to a tax ID cannot start the connection and getsforbidden_action. A tax ID your key does not reach answers404. - The Stripe module in the plan of the tax ID, available in the Empresario and Enterprise plans.
- Return domains registered in your account. They are the only hosts Factuarea will send a person back to.
1. Register your return domains
A return domain is a host you allow as the destination of the return_url. They belong to the account that starts the connection, which is yours, not the account that owns the tax ID.
curl -X POST https://api.factuarea.com/v1/accounts/{account}/connected-accounts/return-domains \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "app.midominio.com"}'It answers 201 with a connect_return_domain object. The rules:
- Host only, up to 253 characters: no scheme, port, path, wildcard, IP address or
localhost. It is lower-cased, soApp.MiDominio.comandapp.midominio.comare the same domain. - Exact match. Registering
midominio.comdoes not allowapp.midominio.com: each subdomain you use is its own entry. - An account can hold up to twenty domains. Registering one that already exists answers
connect_return_domain_already_exists(409) and beyond the limitconnect_return_domain_limit_reached(422). {account}is your own account. Another account, one you manage or one that does not exist answers404 account_not_found.- The key must have been issued by an owner or admin of the account and its tax ID scope must cover every tax ID of the account, otherwise
forbidden_action.
List them with GET on the same path, with cursor pagination, and remove one with DELETE …/return-domains/{return_domain}, which answers 204. Removing a domain does not touch authorizations already started, but any new return_url on that host is rejected, and the return of one already in flight is no longer sent to it (see the return). If the domain does not exist or belongs to another account, the answer is connect_return_domain_not_found.
The same list is managed from the web app, in Developers, under the return domains tab.
2. Start the authorization
POST /v1/companies/{company}/connected-accounts/authorizations starts the connection of one tax ID. Two things are mandatory:
Idempotency-Keyheader. The call creates an account in Stripe, so a retry without the key could create a second one. A missing key answersidempotency_key_requiredonce the transition window of the platform is closed, and until then the call is served with aDeprecationheader. Send it from the first day.return_url, an absolutehttpsaddress of up to 2048 characters, without credentials or fragment and on port 443. Its host must be one of your return domains. Anything else answersconnect_return_url_not_allowed(422) and creates nothing.
curl -X POST https://api.factuarea.com/v1/companies/{company}/connected-accounts/authorizations \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: connect-2026-0001" \
-H "Content-Type: application/json" \
-d '{"return_url": "https://app.midominio.com/stripe/vuelta"}'{
"data": {
"id": "0199a1f0-1c22-7a10-b111-3a3b3c3d3e3f",
"object": "connected_account_authorization",
"url": "https://connect.stripe.com/setup/s/…",
"return_url": "https://app.midominio.com/stripe/vuelta",
"status": "pending",
"expires_at": "2026-10-06T10:00:00+00:00",
"company_id": "0199a1f0-1c22-7a10-b111-4a4b4c4d4e4f"
}
}urlis the Stripe onboarding link. It is not a Factuarea page: open it in the browser of the person who owns the Stripe account. It is a short-lived Stripe Account Link: if it expires or has already been used, Stripe sends the person back to Factuarea, which renews it with a new link for the same Stripe account, so you do not need to start again.- The authorization stays
pendingfor 24 hours (expires_atis the deadline to complete it, not the expiry ofurl). Starting again for the same tax ID from the same account while it is still valid reuses it: you get the sameidand the same Stripe account back with a fresh link, and the state of the previous one stops being valid. - The response has no
Locationheader: an authorization has no read of its own. - The call is limited to 10 per minute per key and company, and a rejection never reaches Stripe.
3. The return to your site
When the person finishes on Stripe, they land on Factuarea for a moment, Factuarea checks the account with Stripe and completes the connection, and the browser is redirected to your return_url keeping its own query and adding these parameters:
| Parameter | On success | On error |
|---|---|---|
status | success | error |
provider | stripe | stripe |
authorization_id | The id of the authorization | The id, when it is known |
company_id | The tax ID, UUID v7 | |
connected_account_id | The id of the connected account, UUID v7 | |
account | The Stripe acct_… | |
error | The public error code | |
error_subcode | The subcode, when there is one |
These are the cases you will see in error:
business_rule_violationwitherror_subcode=onboarding_incompleteoronboarding_pending_review: the person left before Stripe accepted the account. The authorization stayspendinguntil it expires: start again and you get the same authorization with a fresh link.resource_already_existswitherror_subcode=connected_account_in_use: that Stripe account is already connected to another company. Nothing is connected.business_rule_violationwitherror_subcode=connect_authorization_expiredorconnect_authorization_already_used: start a new authorization.
Two situations never send the browser to your site. If you removed the domain of the return_url after starting, the connection still completes if it applies, but the person lands on the Factuarea app instead. The same happens if the return arrives with a value that identifies no authorization. In neither case does Factuarea redirect to a host outside your list.
Read the status and the other parameters as a hint for your screen, not as the source of truth: confirm the connection reading the account.
4. Read the connection
The connected account is then in GET /v1/companies/{company}/connected-accounts, identified by the connected_account_id of the return. It starts with auto-invoicing off and is the main account of the tax ID if there was none. From there it is managed with the usual operations.
What to keep in mind
- Who can start. The account that owns the tax ID, or the provider that manages it with
operateaccess.viewaccess answers403. - No MCP tool. The flow needs a browser redirect, so it is not exposed in the MCP server.
- The state is single-use. The signed value that travels in the return is checked before anything is read and works once. Opening the same return twice never connects anything twice.
- Errors in one place. The codes of this flow are
connect_return_url_not_allowed,connect_return_domain_already_exists,connect_return_domain_limit_reachedandconnect_return_domain_not_found, besides the general ones. See rate limits for what happens when you call too fast.