Skip to navigation

Widget Redirection & Completion

Configure client-side redirection and post-session behaviors once a widget flow finishes

View as Markdown

A Spain Public Administration session lets you control the user’s browser redirection behavior once they complete or exit the data extraction widget. This is done by configuring completion settings during session initialization.

A redirect moves your customer’s browser, not your data. It reaches your web page, not your backend, it carries no records, and it does not happen at all if your customer closes the tab instead of finishing. The session itself is usually not over at that moment either: it finishes when the last administration answers, which can be minutes later. The results reach you the other two ways: a webhook our servers send to yours, or polling the session.

When a session ends, two separate things happen, in two separate places:

So use both: the redirect to put the person back where they belong, and the webhook to find out what came back. And keep their settings apart — the completion URL is per session, the webhook endpoints are configured once on the application.


Redirection Settings

When calling Create Session, you can customize redirection behaviors under the settings object using the following parameters:

  • completion_mode: What the widget does at the end — stay on its final screen, redirect in the same tab, or open a new one: the three modes.
  • completion_url: The destination URL to navigate to (required if redirecting). Supports dynamic template variables in the URL path — the query string is built separately, from completion_extra_data.
  • completion_extra_data: A dictionary of key-value pairs. All keys and values are automatically serialized into query parameters and appended to the final redirect URL. Values in this dictionary support dynamic template variables.

An unregistered completion_url fails the session, not the redirect.
To prevent open-redirect vulnerabilities, the completion_url must match one of the URLs registered on your application. It is checked when the session is created: Create Session answers 400 redirect_url_not_allowed_by_app and no session is created at all. So this is something you configure once, before your first redirecting session — not something a user discovers by never coming back. Where to register them.


Where to register them

1

Open your application's settings

Console → your application → App Settings → the General Settings tab. The Redirect URLs card sits under the application’s details, and the URLs registered right now are listed on it.

Application Settings in the Console, on the General Settings tab, with the Redirect URLs card highlighted below the application's detailsApplication Settings in the Console, on the General Settings tab, with the Redirect URLs card highlighted below the application's details
2

Add the URLs the widget may send people back to

The pencil opens the card for editing. Add URL adds a field, the bin removes one, and Save commits the list — up to 10 URLs per application.

The Redirect URLs card open for editing, with one field per URL, the Add URL button and the remaining-URL countThe Redirect URLs card open for editing, with one field per URL, the Add URL button and the remaining-URL count

What is stored, and how it is matched

  • The query string is dropped on the way in. https://portal.mycompany.com/verify?token=123 is stored as https://portal.mycompany.com/verify. That is deliberate: a real redirect’s query is built at runtime from completion_extra_data, so it cannot be known here — and pinning it would make every session fail.
  • The match is the origin, then a prefix of the path. A completion_url is allowed when it has the same scheme, host and port as one of the registered entries and its path starts with the entry’s path. That lets the runtime query string through, and the path is the knob for how wide you open the door: https://portal.mycompany.com allows every URL on that host, while https://portal.mycompany.com/onboarding/ allows only that branch. A host that only starts like a registered one — https://portal.mycompany.com.example.org — is another host, and is refused. Register the narrowest prefix that covers your flows.
  • Only http and https, and the URL must carry a host. A URL that is not one of those two schemes is refused when you save it.
  • A third-party destination is a redirect URL too. The WhatsApp examples below send the user to https://wa.me/…, which means https://wa.me/ has to be on this list like any other destination.

Completion Modes

You can specify how the widget behaves upon exit by setting completion_mode to one of the following values:

ModeBehaviorDescription
do_nothingRemains on final screenThe widget stops at its final screen, and the user closes the window or tab. Embedded in an iframe, your page hears the es-public-administration:ended message and can close the frame itself.
redirectRedirects current tabAfter a few seconds’ countdown on the final screen, the widget navigates to the completion_url in its own window. Inside an iframe, that is the iframe, not your page.
redirect_blankOpens a new tabAfter the same countdown, the widget opens the completion_url in a new tab with window.open(..., '_blank').

Either way, the final screen keeps a link to the completion_url, for when the browser does not follow the redirect.

Prefer redirect over redirect_blank
Modern web browsers restrict scripts from automatically opening new tabs (window.open(..., '_blank')) unless triggered by a direct user action (like a click). Since widget redirection is automatic upon flow completion, browsers might block redirect_blank as a popup.

In an iframe, leave the redirect to your page
A redirect navigates the widget’s own window, so inside an iframe only the frame moves — and many destinations refuse to be shown in one: WhatsApp, for instance. Set completion_mode to do_nothing and move your page yourself when the es-public-administration:ended message arrives.


Dynamic Query Parameters & Templating

The widget engine supports dynamic runtime template variables to construct customized redirect flows. These settings are configured inside the settings object of the Create Session initialization request payload.

These template variables can be used in two ways:

  1. Directly in the URL: Interpolated within the completion_url string, in the URL path.
  2. In Extra Data values: Interpolated inside the values of the completion_extra_data dictionary.

Additionally, the completion_extra_data dictionary is automatically converted to query parameters and appended to the final URL.

Do not put a query string on completion_url — it is dropped. The URL is stored without it, exactly as registered redirect URLs are, and the final query is assembled from completion_extra_data alone. Everything you want in the query goes there.

Available Template Variables

  • {app_id}: The unique identifier of your application.
  • {customer_id}: The customer ID associated with the session.
  • {session_id}: The active widget session ID.
  • {status_code}: The final status code of the session (e.g. COMPLETED).

Example Parameter Mapping

If you initialize a session with a URL template path and an extra data dictionary (demonstrating how template variables can be partially interpolated inside composite strings, such as prefixing the session ID):

{
"customer_id": "cust_esp_202406",
"settings": {
"completion_mode": "redirect",
"completion_url": "https://your-server.com/onboarding/{customer_id}/verify",
"completion_extra_data": {
"session": "sess_{session_id}",
"status": "{status_code}",
"source": "widget_flow"
}
}
}

The template variables are resolved and the dictionary is serialized into query parameters, redirecting the user to:

https://your-server.com/onboarding/cust_esp_202406/verify?session=sess_644599da47847b79c03cc94f&status=COMPLETED&source=widget_flow

Data Security Warning
All redirect parameters, whether interpolated directly in the completion_url path or passed via the completion_extra_data dictionary, are exposed in the final browser URL query string. This makes them visible in browser history, network logs, and referrer headers. Never pass sensitive personal data (such as names, emails, phones, or DNI numbers) as template variables. Use generic identifiers like {session_id} and fetch the results securely on your backend.


Real-World Examples

The following examples demonstrate how to construct the request payload when calling the Create Session endpoint.

Note that all redirection settings must be nested inside the settings object of the initialization payload, alongside other session configurations (such as required features).

Return to Host App (CRM / Portal)

Redirect the user back to your customer portal, passing the session status and customer ID to automatically refresh their application state:

{
"customer_id": "cust_esp_202406",
"settings": {
"completion_mode": "redirect",
"completion_url": "https://portal.mycompany.com/onboarding/verify",
"completion_extra_data": {
"session_id": "{session_id}",
"customer": "{customer_id}",
"status": "{status_code}"
}
}
}

Resulting URL:
Assuming a session with ID 644599da47847b79c03cc94f, customer ID cust_esp_202406, and status code COMPLETED, the final resolved URL is:

https://portal.mycompany.com/onboarding/verify?session_id=644599da47847b79c03cc94f&customer=cust_esp_202406&status=COMPLETED

Redirecting to WhatsApp

A WhatsApp chat is a destination like any other: the session ends on it, with a message already written for your number — the session ID in it, so whoever answers knows which session is writing. WhatsApp offers two addresses, and either one has to be registered first.

WhatsApp Short URL (wa.me)

The modern way to open a WhatsApp chat is https://wa.me/<<phone>>, with the number in international format, digits only. A message can be written in advance in the text query parameter — the session ID in it tells whoever answers which session is writing:

{
"customer_id": "cust_esp_202406",
"settings": {
"completion_mode": "redirect",
"completion_url": "https://wa.me/34600000000",
"completion_extra_data": {
"text": "I want to apply for my loan. Session ID: {session_id}"
}
}
}

Resulting URL:
The template variables are resolved, and the dictionary’s keys and values are URL-encoded and appended — spaces as +, which WhatsApp reads as spaces. For a session with ID 644599da47847b79c03cc94f, the final resolved URL is:

https://wa.me/34600000000?text=I+want+to+apply+for+my+loan.+Session+ID%3A+644599da47847b79c03cc94f

It needs https://wa.me/ among the application’s redirect URLs.

WhatsApp Legacy Endpoint

The legacy WhatsApp endpoint (https://api.whatsapp.com/send) takes the phone number in completion_extra_data too, with the language and the message:

{
"customer_id": "cust_esp_202406",
"settings": {
"completion_mode": "redirect",
"completion_url": "https://api.whatsapp.com/send",
"completion_extra_data": {
"phone": "34600000000",
"l": "en",
"text": "I want to apply for my loan. Session ID: {session_id}"
}
}
}

Resulting URL:
For a session with ID 644599da47847b79c03cc94f, the query parameters are serialized, URL-encoded and appended:

https://api.whatsapp.com/send?phone=34600000000&l=en&text=I+want+to+apply+for+my+loan.+Session+ID%3A+644599da47847b79c03cc94f

It needs https://api.whatsapp.com/send among the application’s redirect URLs.

WhatsApp opens in the person’s browser or app, never inside a frame. If you embed the widget in an iframe, send the person to WhatsApp from your own page when the es-public-administration:ended message arrives.


Iframe postMessage Events

If you embed the widget inside an HTML <iframe> on your website, the widget will dispatch a JavaScript message event to the parent window upon completion or exit, regardless of the configured completion_mode. This is useful for programmatically closing the iframe, refreshing parent page components, or showing custom UI elements on the host platform.

Event Message Structure

When the session reaches its end screen, the widget calls window.parent.postMessage() with a payload structured as follows:

  • event: The event identifier string (always es-public-administration:ended).
  • status_code: The final execution status of the session (e.g. COMPLETED, CUSTOMER_CANCELLED, WAITING). Who ended the session, status by status, is in How a session ends.
  • cursor: The identifier of the active cursor/screen the user was on when the widget session finished. Possible values include:
    • es-public-administration:bye:completed: The user successfully completed the data retrieval and signature flow.
    • es-public-administration:bye:failure: The session ended without connecting any administration.
    • es-public-administration:connect:wait: The session is temporarily waiting for an asynchronous background task or report generation (e.g., generating the CIRBE report).

The widget closing is not the session ending. When status_code is WAITING and the cursor is es-public-administration:connect:wait, the user is free to go but a source — typically the CIRBE report — is still working in the background.

This browser message tells you the widget is gone; it does not carry results, and it is not a completion signal — the same distinction this page opens with. The data reaches your backend through webhooks: es-public-administration:partial with what was gathered so far, and es-public-administration:ended with the final data once the pending source answers.

A session is finished when — and only when — is_closed is true. See Knowing when a session is finished.

Integration Example

To handle the completion event in your host application, register a listener for the window message event:

// The widget's own origin: the one of the `widget_url` Create Session returned
const WIDGET_ORIGIN = new URL(widgetUrl).origin;
window.addEventListener("message", (event) => {
// Recommended: accept messages from the widget only
if (event.origin !== WIDGET_ORIGIN) return;
const payload = event.data;
// Check if the message is the completion event
if (payload && payload.event === "es-public-administration:ended") {
const { status_code, cursor } = payload;
console.log(`Widget session ended with status: ${status_code} at cursor: ${cursor}`);
// Programmatically close your iframe modal or update UI
if (status_code === "COMPLETED") {
// Refresh user onboarding state or fetch data securely from your backend
} else {
// Handle exit/failure scenarios
}
}
});