Integración
Esta página es la guía para consumidores que embeben mas-consents desde otra aplicación.
Describe el contrato de URL, mensajería con el host, llamadas backend que realiza el componente y el listener mínimo que debería implementar un host.
Destinos de embebido
mas-consents puede embeberse en:
- un iframe de navegador
- un flujo browser opener/popup
WebViewde React NativeWKWebViewde iOSWebViewde Android
Autenticación del componente embebido
Token esperado por el componente
El parámetro token de la URL es el token que mas-consents reenvía al backend de Consents como Authorization: Bearer <token>.
En integraciones expuestas a clientes, el host no debería pasar en la URL un token amplio del canal, de agente o de cliente.
La URL puede quedar visible temporalmente en navegador, WebView, histórico, trazas o logs de infraestructura antes de que el componente limpie la query string.
Obtención de un token securizado
En canales públicos, el componente debe recibir un token acotado al flujo embebido de consentimientos. Ese token se obtiene mediante el endpoint de securización de Consents, disponible en la referencia de la API de Consents:
POST /v2/orgs/{brand}/securizeLlama a este endpoint con credenciales autorizadas para securizar el payload del frontend embebido, usando el rol consents-front-operator.
Esas credenciales no deben exponerse en código cliente público.
El body debe incluir el contexto que Consents necesita para acotar la autorización:
{
"actor_id": "<actor_id>",
"customer_id": "<customer_id>"
}La respuesta contiene el token esperado por el frontend embebido:
{
"web_component_token": "<token_for_mas_consents>"
}Uso del token en la URL
Ese valor se pasa en la URL como token=<token_for_mas_consents>.
mas-consents lo usa para sus llamadas GET y POST de consentimientos.
El token sigue siendo sensible, aunque esté acotado al dominio de Consents y al contexto autorizado. El host debe evitar escribir la URL completa en logs, errores, analítica o herramientas de soporte.
Parámetros de URL
El frontend lee la query string una sola vez durante el arranque. Para llamadas reales al backend, el host debe proporcionar autenticación, contexto de cliente y datos de trazabilidad suficientes.
Autenticación
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
token | Se reenvía como Authorization: Bearer <token> cuando está presente | Obligatorio para llamadas reales al backend |
En canales públicos, usa el web_component_token devuelto por la securización.
Contexto de cliente
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
customerId | Se usa en las rutas GET/POST | Obligatorio para llamadas reales al backend |
brand | Se usa en las rutas GET/POST. mock activa comportamiento mock local | Obligatorio para llamadas reales al backend |
sector | Se reenvía como header x-sector | Obligatorio para llamadas reales al backend |
Selección del flujo
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
view | Los parámetros repetidos se preservan y se envían como query params repetidos | Obligatorio en la mayoría de flujos host |
choice | Filtro opcional que se pasa al backend | Opcional |
Localización
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
lang | Se normaliza contra la lista de locales soportados. Si falta o es inválido, cae a es | Recomendado |
locales | Lista opcional separada por comas. Por defecto usa todos los locales soportados | Opcional |
Submit y trazabilidad
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
actorId | Se incluye en el body POST como actor_id | Esperado para flujos reales de submit |
sellChannel | Se incluye en el body POST como sell_channel | Esperado para flujos reales de submit |
traceId | Se incluye en el body POST como trace_id | Esperado para trazabilidad y submit |
Comunicación y personalización
| Parámetro | Comportamiento de runtime | Expectativa para consumidores |
|---|---|---|
enableCustomConfig | Solo el valor exacto true tiene efecto. Hace que el componente espere configuración JSON del host | Opcional |
hostCommunication | opener activa el modo de comunicación con browser opener | Opcional |
hostOrigin | Obligatorio con hostCommunication=opener; los orígenes inválidos se ignoran | Opcional salvo que se use modo opener |
Ejemplo base de URL sin personalización:
https://consents.masstack.com/?token=<web_component_token>&customerId=<customer_id>&brand=<brand>§or=<sector>&view=MANDATORY&lang=es&actorId=<actor_id>&sellChannel=<channel>&traceId=<trace_id>Construye la URL con URLSearchParams o una API equivalente para codificar correctamente el token y evitar concatenaciones manuales.
Locales soportados:
eseucaengl
Valores soportados de view:
MANDATORYPORTFOLIOSGDAINTERNALPRIVATEWHATSAPPOGW_DEVICE_SWAPOGW_SIM-SWAPOGW_NUMBER-VERIFICATIONOGW_KYC-MATCH
Varios views se pasan como parámetros repetidos:
?view=MANDATORY&view=PORTFOLIOValores soportados de choice:
PENDINGREJECTEDACCEPTED
Flujos habituales
El valor de view depende del caso de negocio que el host quiera resolver.
Como referencia:
- en un alta o contratación, el host suele solicitar consentimientos obligatorios con
view=MANDATORY - en una pantalla de gestión o modificación de consentimientos, el host debe usar la vista acordada para ese canal, por ejemplo una vista de cartera
- si el host quiere limitar la carga a consentimientos con un estado concreto, puede combinar la vista con
choice
Cuando el backend debe recibir más de una vista, repite el parámetro view en la URL en lugar de enviar una cadena separada por comas.
Limpieza de cadena de consulta
El componente lee los parámetros de URL durante el arranque y después limpia la cadena de consulta de la URL del navegador.
El contexto de arranque se conserva durante la sesión del navegador para que el componente pueda recuperarse tras una recarga.
La única excepción es brand=mock, que conserva la cadena de consulta visible para facilitar depuración local.
Mensajería con el host
El bridge emite mensajes estructurados y mantiene eventos de texto del navegador por compatibilidad. Las integraciones nuevas deberían soportar mensajes estructurados.
Mensajes estructurados salientes
Los hosts en iframe reciben mensajes estructurados como objetos:
{
"source": "consents",
"version": "v1",
"type": "CONSENTS_LOADED",
"payload": {}
}React Native, Android WebView, iOS WebView y los flujos browser opener reciben el mismo payload serializado como texto JSON.
Los hosts de esos entornos deben parsear el texto antes de leer source o type.
Tipos de mensaje saliente:
CONSENTS_CONFIG_REQUIRED: se emite solo cuandoenableCustomConfig=trueCONSENTS_LOADED: se emite después de cargar consentimientosCONSENTS_COMPLETED: se emite tras un submit correcto o cuando el backend indica que la campaña ya está completadaCONSENTS_ERROR: se emite con payload de error backend cuando falla la carga o el submit
Eventos de texto de compatibilidad
Los hosts en iframe también reciben mensajes de texto planos:
consents-loadedconsents-successconsents-error
Estos eventos existen por compatibilidad con integraciones host antiguas. No deberían ser el único contrato para consumidores nuevos.
Mensajes entrantes
El componente acepta actualmente un mensaje enviado por el host:
SET_CONFIG
SET_CONFIG solo es obligatorio cuando la URL contiene enableCustomConfig=true.
Sin ese flag, el componente renderiza inmediatamente con el tema por defecto e ignora SET_CONFIG inbound.
El mensaje puede enviarse como objeto en integraciones iframe o como texto JSON en integraciones de bridge nativo:
{
"type": "SET_CONFIG",
"payload": {
"theme": "light"
}
}Escucha recomendada para navegador
const iframe = document.querySelector<HTMLIFrameElement>('#mas-consents')
const consentsOrigin = new URL(iframe?.src ?? 'https://consents.masstack.com').origin
window.addEventListener('message', (event) => {
if (event.origin !== consentsOrigin) return
const data = event.data
if (typeof data === 'string') {
if (data === 'consents-loaded') {
// Señal de ready de compatibilidad
}
if (data === 'consents-success') {
// Señal de finalización de compatibilidad
}
if (data === 'consents-error') {
// Señal de error de compatibilidad
}
return
}
if (data?.source !== 'consents') return
switch (data.type) {
case 'CONSENTS_CONFIG_REQUIRED':
iframe?.contentWindow?.postMessage(
{
type: 'SET_CONFIG',
payload: {
theme: 'light',
},
},
'*',
)
break
case 'CONSENTS_LOADED':
// Señal ready
break
case 'CONSENTS_COMPLETED':
// Señal de finalización
break
case 'CONSENTS_ERROR':
// El payload de error backend está disponible en data.payload
break
}
})Las aplicaciones host deben validar event.origin según su propio modelo de seguridad.
Llamadas HTTP que realiza el componente
El host no llama directamente al backend de consents para el flujo embebido. El componente realiza estas llamadas usando el contexto de URL.
GET consents
GET /v2/orgs/{brand}/customers/{customerId}/consentsHeaders reenviados:
Authorization: Bearer <token>cuando existetokenAccept-Languagedesde el locale normalizadox-sectordesde la URLx-keysi fue devuelto por una respuesta anterior
Query params reenviados:
viewrepetidochoiceopcionalconsent_idsopcional cuando se proporcionaCustomConfig.consentIdsdisplay_format=SHORTopcional en móvilxs
POST consents
POST /v2/orgs/{brand}/customers/{customerId}/consentsHeaders reenviados:
Authorization: Bearer <token>cuando existetokenAccept-Languagex-sectorx-keycapturado de la respuesta GET previa
El body POST incluye actor_id, sell_channel, trace_id, selecciones del cliente, IP y fecha de captura.
Configuración de plataforma de runtime
El frontend también carga /config.json al arrancar.
Este archivo lo aporta el despliegue y normalmente no lo suministra el host consumidor.
Campos soportados:
ENVAPI_GATEWAYFARO_COLLECTOR_URLIS_OBSERVABILITY_ENABLEDTRACING_WHITELIST_URLS
Si /config.json no puede cargarse y el runtime sigue en LOCAL, se mantiene la configuración local por defecto.
Ejemplos
Los ejemplos siguientes usan enableCustomConfig=true para mostrar el handshake completo.
Si el host no necesita personalizar el componente, elimina enableCustomConfig=true de la URL y no envíes SET_CONFIG.
Iframe de navegador
<iframe
id="mas-consents"
src="https://consents.masstack.com/?token=JWT&customerId=123&brand=masmovil§or=telco&view=MANDATORY&lang=es&actorId=agent-123&sellChannel=ecare&traceId=trace-123&enableCustomConfig=true"
style="width: 100%; height: 900px; border: 0;"
></iframe>
<script>
const iframe = document.getElementById('mas-consents')
const consentsOrigin = new URL(iframe.src).origin
window.addEventListener('message', (event) => {
if (event.origin !== consentsOrigin) return
const data = event.data
if (typeof data === 'string') {
if (data === 'consents-success') {
window.location.href = '/next-step'
}
if (data === 'consents-error') {
console.error('Consents failed')
}
return
}
if (data?.source !== 'consents') return
if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
iframe.contentWindow?.postMessage(
{
type: 'SET_CONFIG',
payload: {
theme: 'light',
uiOptions: {
groupDisplay: 'flat',
collapsible: true,
},
},
},
'*',
)
}
if (data.type === 'CONSENTS_COMPLETED') {
window.location.href = '/next-step'
}
if (data.type === 'CONSENTS_ERROR') {
console.error(data.payload)
}
})
</script>Popup/opener de navegador
const consentsUrl = new URL('https://consents.masstack.com/')
consentsUrl.search = new URLSearchParams({
token: 'JWT',
customerId: '123',
brand: 'masmovil',
sector: 'telco',
view: 'MANDATORY',
lang: 'es',
actorId: 'agent-123',
sellChannel: 'ecare',
traceId: 'trace-123',
enableCustomConfig: 'true',
hostCommunication: 'opener',
hostOrigin: window.location.origin,
}).toString()
const popup = window.open(consentsUrl.toString(), 'mas-consents', 'width=480,height=900')
window.addEventListener('message', (event) => {
if (event.origin !== 'https://consents.masstack.com') return
const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
if (data?.source !== 'consents') return
if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
popup?.postMessage(
JSON.stringify({
type: 'SET_CONFIG',
payload: {
theme: 'light',
},
}),
'https://consents.masstack.com',
)
}
if (data.type === 'CONSENTS_COMPLETED') {
popup?.close()
}
})React Native WebView
import { useRef } from 'react'
import { WebView } from 'react-native-webview'
const consentsUrl =
'https://consents.masstack.com/?token=JWT&customerId=123&brand=masmovil§or=telco&view=MANDATORY&lang=es&actorId=agent-123&sellChannel=app&traceId=trace-123&enableCustomConfig=true'
export function ConsentsScreen() {
const webViewRef = useRef<WebView>(null)
const sendConfig = () => {
const message = JSON.stringify({
type: 'SET_CONFIG',
payload: {
theme: 'light',
uiOptions: {
groupDisplay: 'flat',
},
},
})
webViewRef.current?.injectJavaScript(`
window.dispatchEvent(new MessageEvent('message', { data: ${JSON.stringify(message)} }));
true;
`)
}
return (
<WebView
ref={webViewRef}
source={{ uri: consentsUrl }}
onMessage={(event) => {
const data = JSON.parse(event.nativeEvent.data)
if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
sendConfig()
}
if (data.type === 'CONSENTS_COMPLETED') {
// Continuar el flujo host
}
if (data.type === 'CONSENTS_ERROR') {
// Gestionar data.payload
}
}}
/>
)
}iOS WKWebView
import UIKit
import WebKit
final class ConsentsViewController: UIViewController, WKScriptMessageHandler {
private lazy var webView: WKWebView = {
let contentController = WKUserContentController()
contentController.add(self, name: "consentsBridge")
let config = WKWebViewConfiguration()
config.userContentController = contentController
return WKWebView(frame: .zero, configuration: config)
}()
override func viewDidLoad() {
super.viewDidLoad()
view = webView
var components = URLComponents(string: "https://consents.masstack.com/")!
components.queryItems = [
URLQueryItem(name: "token", value: "JWT"),
URLQueryItem(name: "customerId", value: "123"),
URLQueryItem(name: "brand", value: "masmovil"),
URLQueryItem(name: "sector", value: "telco"),
URLQueryItem(name: "view", value: "MANDATORY"),
URLQueryItem(name: "lang", value: "es"),
URLQueryItem(name: "actorId", value: "agent-123"),
URLQueryItem(name: "sellChannel", value: "ios"),
URLQueryItem(name: "traceId", value: "trace-123"),
URLQueryItem(name: "enableCustomConfig", value: "true")
]
webView.load(URLRequest(url: components.url!))
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let raw = message.body as? String,
let data = raw.data(using: .utf8),
let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
payload["source"] as? String == "consents"
else { return }
if payload["type"] as? String == "CONSENTS_CONFIG_REQUIRED" {
let config = #"{"type":"SET_CONFIG","payload":{"theme":"light"}}"#
webView.evaluateJavaScript(
"window.dispatchEvent(new MessageEvent('message', { data: '\(config)' }));"
)
}
}
}Android WebView
class ConsentsActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this)
setContentView(webView)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(ConsentsBridge(), "NativeBridge")
val url = Uri.parse("https://consents.masstack.com/").buildUpon()
.appendQueryParameter("token", "JWT")
.appendQueryParameter("customerId", "123")
.appendQueryParameter("brand", "masmovil")
.appendQueryParameter("sector", "telco")
.appendQueryParameter("view", "MANDATORY")
.appendQueryParameter("lang", "es")
.appendQueryParameter("actorId", "agent-123")
.appendQueryParameter("sellChannel", "android")
.appendQueryParameter("traceId", "trace-123")
.appendQueryParameter("enableCustomConfig", "true")
.build()
webView.loadUrl(url.toString())
}
inner class ConsentsBridge {
@JavascriptInterface
fun postMessage(raw: String) {
val message = JSONObject(raw)
if (message.optString("source") != "consents") return
if (message.optString("type") == "CONSENTS_CONFIG_REQUIRED") {
val config = JSONObject()
.put("type", "SET_CONFIG")
.put("payload", JSONObject().put("theme", "light"))
.toString()
runOnUiThread {
webView.evaluateJavascript(
"window.dispatchEvent(new MessageEvent('message', { data: ${JSONObject.quote(config)} }));",
null,
)
}
}
}
}
}