JouloDocs

Onboarding inside your app

Take a customer from your app to a submitted ERE registration and back, without our portal

This guide is for partners with their own app. Your customer never sees the Joulo site or dashboard. They open one screen from your app, finish their registration there, and land back in your app.

The screen is ours. The customer signs the machtiging with Joulo B.V., and we have to show the NEa who signed, when, and which clause text they saw. Your logo sits next to ours at the top.

The sequence

  1. Upload the customer with POST /partner/customers and send_invite: false. Send the address, and for BlueBird Power the charger id. See Upload customers.
  2. Register your return URL once, in the partner portal under API-koppeling. It switches the in-app flow on. See Your return URL.
  3. Mint an auth-link with POST /partner/drafts/auth-link the moment the customer opens the screen.
  4. Open auth_url in an in-app browser. See Which browser to use.
  5. Catch the return URL. We send the customer there with a status.
  6. Confirm with the API. Read GET /partner/customers and check registration_status.
curl -X POST https://api.joulo.nl/functions/v1/api/partner/drafts/auth-link \
  -H "Authorization: Bearer jpk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "draft_id": "8f2c...", "return_url": "bluebird://joulo/done" }'
{
  "ok": true,
  "auth_url": "https://joulo.nl/claim-auth/...",
  "expires_in": 300,
  "resume": false,
  "return_url": "bluebird://joulo/done"
}

The link is a login credential and lives 5 minutes. Never store it and never email it. Mint a fresh one each time the customer opens the screen. A new link makes the previous one invalid.

What the customer does

The link signs the customer in. They then walk through these steps:

StepWhat happensWhen it shows
WelcomeThe customer confirms they want to continue. Business customers enter their KvK number.First visit only
ChargerBlueBird Power customers type their charger id, checked live.Only when no charger is linked yet
RegistrationName, mobile number, address, connection (EAN), IBAN and the machtiging.Always, until submitted
Done"Je aanmelding is binnen", then back to your app.After submit

What you sent at upload shortens the registration:

  • The address fills in postcode and house number. We look up street, city and EAN from it.
  • One EAN on the address is filled in for them. With several, the customer picks one. They always confirm that their charger sits behind that EAN.
  • A pre-coupled charger skips the charger step completely.

The customer signs the machtiging once, on the last step of the registration. It names the EAN and the holder of the connection. Nothing before that step binds anything.

Business customers (zakelijk, vve) also upload a KvK extract and an energy bill in the flow. Those documents cannot come through the API.

Your return URL

Register it once in the partner portal, page API-koppeling. It is your app's deep link (bluebird://joulo) or an https address you own. No query string and no fragment: we add ?status= ourselves.

  • An API key cannot set it. A leaked key must not be able to point the end of our flow at a page of its choosing.
  • GET /partner/me returns it as app_return_url, so your integration can check it.
  • An auth-link may carry its own return_url, but it must start with the registered one, followed by /, ? or nothing.
  • Without a registered URL, the auth-link opens the classic claim page. That page ends in the Joulo dashboard, not in your app.
  • The done screen always tells the customer they can close it, and then opens your URL.
ErrorStatusMeaning
return_url_not_registered400You sent return_url, but the portal has none.
return_url_not_allowed400The URL does not start with the registered one.

What comes back to your app

We open your return URL with a status, and sometimes a reason:

statusMeaningWhat to do
submittedThe registration is in.Close the browser. Confirm with GET /partner/customers.
closedThe customer tapped "Terug naar …" before finishing.Offer to continue later.
resumeThe customer verified with their bank and came back through the system browser. reason=idin.Mint a new auth-link and open it again.
failedThe flow cannot continue. reason says why.Show your own message. Contact us for conflict or ean_conflict.

reason values for failed: expired, conflict, ean_conflict, not_found, invalid_token, not_claimed, finish_failed. With finish_failed the registration may well be in: check the API.

Treat status as a hint. Anyone can open a URL with ?status=submitted. The API is the source of truth.

Picking up where they left off

Customers leave halfway. An auth-link works again on the same draft while the account is still empty: a concept registration, no IBAN stored and no verified identity.

  • resume: true in the response means the draft was already activated.
  • The form saves as the customer types, so it opens with their answers filled in.
  • After that, the link is refused with 409 draft_not_claimable and a reason. A partner key must never be a login to an account that holds a payout account, a verified identity or a signed machtiging.
  • The customer then signs in themselves. See the next section.
reason on draft_not_claimableMeaning
registration_submittedDone. The customer signs in themselves from now on.
account_has_detailsThe customer already entered an IBAN or verified their identity.
own_accountThe draft sits on an account this link did not create, such as the customer's existing Joulo account. We never log in to that one.
account_closedThe account no longer exists.

Customers who already have an account

An upload for an address that already has a Joulo account is refused with existing_user. An account can still appear between your upload and the customer's first tap. The link then logs nobody in, because a partner link must never open an account the customer already had. The screen asks the customer to sign in themselves.

The same happens when the EAN we found at the address already carries a submitted registration. That can be this customer under another email address, or the previous occupant. The screen explains both. You do not see this status: a partner can upload any address, and it must not learn which households are Joulo customers.

The screen offers Google, Apple and an email link. Google refuses to sign in inside a bare webview, but works in the in-app browsers below. An email link opens from the mail app, so the flow continues in the system browser. It still ends at your return URL.

Bank verification (iDIN)

Some accounts verify their identity with their bank before signing. It is off for most customers today. When it is on, the bank step leaves your app:

  1. The bank app opens.
  2. The bank sends the customer to the system browser, not back to your app.
  3. That page shows a "Terug naar …" button to your return URL, with status=resume&reason=idin.
  4. You mint a new auth-link. Our screen waits up to three minutes until the verification is in, then continues.

Which browser to use

Use the in-app browser of the platform, not a bare webview:

  • iOS: ASWebAuthenticationSession, with callbackURLScheme set to your scheme. It closes itself when we open your return URL.
  • Android: Custom Tabs, with an intent filter on your return URL.

A bare WKWebView or Android WebView works for the flow itself. You then have to do two things yourself:

  • Watch navigations (decidePolicyFor / shouldOverrideUrlLoading), and close the view when it reaches your return URL.
  • Let bank links open outside the view. Otherwise the bank app never opens, and iDIN stays on the bank's website.

Tracking progress

PhaseEndpointField
UploadPOST /partner/customerscreated[].ean_status
ClaimGET /partner/drafts?external_ref=status: invitedclaimedactive
RegistrationGET /partner/customersregistration_status: conceptingediendin_reviewgoedgekeurd
MoneyGET /partner/ere-positionsper EAN, once eligible sessions exist

Draft active means the customer started. registration_status: ingediend means they finished. There is no webhook: poll a few times a day, and once right after status=submitted.