Integration
This page is the consumer guide for embedding mas-consents from another application.
It covers the URL contract, host messaging, backend calls triggered by the component, and the minimum listener a host should implement.
Embedding targets
mas-consents can be embedded in:
- a browser iframe
- a browser opener/popup flow
- React Native
WebView - iOS
WKWebView - Android
WebView
URL parameters
The frontend reads the query string once during startup. For real backend calls, the host must provide enough context for the consents API.
| Parameter | Runtime behavior | Consumer expectation |
|---|---|---|
token | Forwarded as Authorization: Bearer <token> when present | Required for real backend calls |
customerId | Used in GET/POST paths | Required for real backend calls |
brand | Used in GET/POST paths. mock enables local mock behavior | Required for real backend calls |
sector | Forwarded as x-sector header | Required for real backend calls |
view | Repeated params are preserved and sent as repeated query params | Required by most host flows |
lang | Normalized against the supported locale list. Falls back to es when missing or invalid | Recommended |
locales | Optional comma-separated list. Defaults to all supported locales. Invalid entries are discarded | Optional |
choice | Optional filter passed to the backend | Optional |
actorId | Included in POST body as actor_id | Expected for real submit flows |
sellChannel | Included in POST body as sell_channel | Expected for real submit flows |
traceId | Included in POST body as trace_id | Expected for tracing and submit flows |
enableCustomConfig | Only exact value true has an effect. It makes the component wait for host JSON configuration | Optional |
hostCommunication | opener enables the browser opener communication mode | Optional |
hostOrigin | Required with hostCommunication=opener; invalid origins are ignored | Optional unless opener mode is used |
Supported locales:
eseucaengl
Supported view values:
MANDATORYPORTFOLIOSGDAINTERNALPRIVATEWHATSAPPOGW_DEVICE_SWAPOGW_SIM-SWAPOGW_NUMBER-VERIFICATIONOGW_KYC-MATCH
Multiple views are passed as repeated params:
?view=MANDATORY&view=PORTFOLIOSupported choice values:
PENDINGREJECTEDACCEPTED
Query string cleanup
The component reads the URL params during bootstrap and then clears the query string from the browser URL.
The only exception is brand=mock, which keeps the query string visible for local debugging.
Host messaging
The bridge emits structured messages and also keeps plain string browser events for compatibility. New integrations should support structured messages.
Outbound structured messages
Browser iframe hosts receive structured messages as objects:
{
"source": "consents",
"version": "v1",
"type": "CONSENTS_LOADED",
"payload": {}
}React Native, Android WebView, iOS WebView and browser opener flows receive the same payload serialized as JSON text.
Hosts in those environments should parse the received string before reading source or type.
Outbound message types:
CONSENTS_CONFIG_REQUIRED: emitted only whenenableCustomConfig=trueCONSENTS_LOADED: emitted after consents are loadedCONSENTS_COMPLETED: emitted after a successful submit or when the backend says the campaign is already completeCONSENTS_ERROR: emitted with backend error payload when loading or submitting fails
Compatibility string events
Browser iframe hosts also receive plain string messages:
consents-loadedconsents-successconsents-error
These events exist for compatibility with older host integrations. They should not be the only contract for new consumers.
Inbound messages
The component currently accepts one host message:
SET_CONFIG
SET_CONFIG is required only when the URL contains enableCustomConfig=true.
Without that flag, the component renders immediately with the default theme and ignores inbound SET_CONFIG.
The message can be sent as an object in browser iframe integrations or as JSON text in native bridge integrations:
{
"type": "SET_CONFIG",
"payload": {
"theme": "light"
}
}Recommended browser listener
const iframe = document.querySelector<HTMLIFrameElement>('#mas-consents')
window.addEventListener('message', (event) => {
const data = event.data
if (typeof data === 'string') {
if (data === 'consents-loaded') {
// Compatibility ready signal
}
if (data === 'consents-success') {
// Compatibility completion signal
}
if (data === 'consents-error') {
// Compatibility error signal
}
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':
// Ready signal
break
case 'CONSENTS_COMPLETED':
// Completion signal
break
case 'CONSENTS_ERROR':
// Backend error payload is available in data.payload
break
}
})Host applications should still validate event.origin according to their own security model.
HTTP calls made by the component
The host does not call the consents backend directly for the embedded flow. The component makes these calls using the URL context.
GET consents
GET /v2/orgs/{brand}/customers/{customerId}/consentsForwarded headers:
Authorization: Bearer <token>whentokenexistsAccept-Languagefrom normalized localex-sectorfrom the URLx-keyif it was previously returned by an earlier response
Forwarded query params:
- repeated
view - optional
choice - optional
consent_idswhenCustomConfig.consentIdsis provided - optional
display_format=SHORTon mobilexs
POST consents
POST /v2/orgs/{brand}/customers/{customerId}/consentsForwarded headers:
Authorization: Bearer <token>whentokenexistsAccept-Languagex-sectorx-keycaptured from the previous GET response
The POST body includes actor_id, sell_channel, trace_id, customer selections, IP and capture date.
Runtime platform config
The frontend also loads /config.json at startup.
This file is provided by the deployment and is not normally supplied by the consumer host.
Supported fields:
ENVAPI_GATEWAYFARO_COLLECTOR_URLIS_OBSERVABILITY_ENABLEDTRACING_WHITELIST_URLS
If /config.json cannot be loaded and the runtime stays in LOCAL, the default local config remains active.
Examples
The examples below use enableCustomConfig=true so they show the complete handshake.
If the host does not need to customize the component, remove enableCustomConfig=true from the URL and do not send SET_CONFIG.
Browser iframe
<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')
window.addEventListener('message', (event) => {
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>Browser opener / popup
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') {
// Continue host flow
}
if (data.type === 'CONSENTS_ERROR') {
// Handle 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,
)
}
}
}
}
}