Embedded Integration
Embed Vespid in your app using WebView or iframe.
Overview
- Auth Modes: Native or API
- Setup Time: 1-2 hours
- Best For: Seamless in-app experience
User Workflow
- Native Auth
- API Auth (SSO)
- User opens your app
- Your app loads Vespid WebView with partner param
- User creates account or logs in on Vespid
- User is associated with your partner
- User starts learning
- User logs into your app
- Your backend requests SSO token from Vespid API
- Your app loads Vespid WebView with partner param
- Your app sends token to WebView via postMessage
- User is automatically authenticated — no login required
- User starts learning
Prerequisites
- Partner Registration Code — Your unique partner identifier (required for all integrations)
- API Key — Secret key for SSO token generation (API Auth only)
Platform dependencies:
- React Native:
react-native-webview - Cordova:
cordova-plugin-inappbrowser - Android: WebView (built-in)
- iOS: WKWebView (built-in)
Contact your Vespid account manager to obtain your Partner Registration Code and API Key.
Auth Options
| Native Auth | API Auth | |
|---|---|---|
| User creates Vespid account | Yes | No (automatic) |
| Requires API key | No | Yes |
| SSO with your app | No | Yes |
Native Auth
Load Vespid with your partner param. Users authenticate on Vespid.
https://integration.vespid.my?partner=<partner_registration_code>
Examples below use production URLs. For staging, use https://staging-integration.vespid.my and https://api-staging.vespid.my. See Environments.
API Auth
Step 1: Get Token (Backend)
Call the Vespid SSO API from your backend to get a Firebase custom token.
Endpoint
POST https://api.vespid.my/auth/partners/sso/token
Request Headers
| Header | Value | Required |
|---|---|---|
X-API-Key | Partner API key | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
email | string | User's email address | One of email or external_user_id required |
external_user_id | string | Your system's unique user identifier | One of email or external_user_id required |
display_name | string | User's display name | No |
// Using email
{
"email": "user@example.com",
"display_name": "John Doe"
}
// Using external_user_id
{
"external_user_id": "usr_12345",
"display_name": "John Doe"
}
Response
{
"success": true,
"data": {
"custom_token": "eyJhbGciOi...",
"expires_at": "2024-01-15T11:30:00Z"
}
}
Token is valid for 1 hour. Generate a new token per session.
Step 2: Send Token to WebView
After WebView loads, send the token via postMessage:
{
type: 'AUTH_TOKEN',
payload: { token: 'eyJhbGciOi...' }
}
The WebView requests a token by sending:
{ type: 'REQUEST_AUTH_TOKEN' }
The WebView re-sends REQUEST_AUTH_TOKEN every ~2 seconds until it receives a token. This covers the case where your app hasn't fetched the token yet when the first request arrives.
Your handler must reply to every request you receive, for as long as the WebView is open — not just the first one. Do not use a "send once" flag (e.g. a tokenSent guard). If you only reply once and that reply is missed or arrives before your token is ready, the user will be stuck unauthenticated with no way to recover short of reloading.
If you don't have a token yet when a request arrives, simply ignore it — a later request will succeed once your token is available.
Platform Setup
- React Native
- Cordova
- Android (Java)
- iOS (Swift)
import React, { useRef, useCallback, useState } from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
const VESPID_URL = 'https://integration.vespid.my';
interface Props {
partnerParam: string;
token?: string; // For API auth
}
export const VespidWebView: React.FC<Props> = ({ partnerParam, token }) => {
const webViewRef = useRef<WebView>(null);
const [loading, setLoading] = useState(true);
// Injects the token via window.postMessage(..., window.location.origin) so the
// resulting MessageEvent has event.source === window and a same-origin event.origin,
// which is required to pass Vespid's message source/origin validation. Sending via
// WebView.postMessage() directly does not satisfy this and will be silently rejected.
const sendToken = useCallback((authToken: string) => {
if (!webViewRef.current) return;
const message = { type: 'AUTH_TOKEN', payload: { token: authToken } };
webViewRef.current.injectJavaScript(`
window.postMessage(${JSON.stringify(message)}, window.location.origin);
true;
`);
}, []);
// Reply to every REQUEST_AUTH_TOKEN — the WebView re-sends this every ~2s
// until it gets a token, so replies must not be gated behind a "sent once" flag.
const handleMessage = useCallback((e: WebViewMessageEvent) => {
if (!token) return;
try {
const data = JSON.parse(e.nativeEvent.data);
if (data.type === 'REQUEST_AUTH_TOKEN') {
sendToken(token);
}
} catch {}
}, [token, sendToken]);
return (
<View style={styles.container}>
<WebView
ref={webViewRef}
source={{ uri: `${VESPID_URL}?partner=${partnerParam}` }}
onMessage={handleMessage}
onLoadEnd={() => setLoading(false)}
javaScriptEnabled
domStorageEnabled
/>
{loading && (
<View style={styles.loading}>
<ActivityIndicator size="large" color="#F4B618" />
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
loading: { ...StyleSheet.absoluteFillObject, justifyContent: 'center', alignItems: 'center' },
});
Usage:
// Native Auth
<VespidWebView partnerParam="your_company" />
// API Auth
<VespidWebView partnerParam="your_company" token={ssoToken} />
const Vespid = {
URL: 'https://integration.vespid.my',
browser: null,
open(partnerParam, token = null) {
this.browser = cordova.InAppBrowser.open(
`${this.URL}?partner=${partnerParam}`,
'_blank',
'location=no,toolbar=yes,hideurlbar=yes'
);
if (token) {
this.browser.addEventListener('loadstop', () => this.sendToken(token));
this.browser.addEventListener('message', (e) => {
if (e.data?.type === 'REQUEST_AUTH_TOKEN') this.sendToken(token);
});
}
},
sendToken(token) {
this.browser?.executeScript({
code: `window.postMessage(${JSON.stringify({
type: 'AUTH_TOKEN',
payload: { token }
})}, '*');`
});
},
close() {
this.browser?.close();
}
};
// Native Auth
Vespid.open('your_company');
// API Auth
Vespid.open('your_company', ssoToken);
public class VespidWebViewActivity extends AppCompatActivity {
private static final String VESPID_URL = "https://integration.vespid.my";
private WebView webView;
private String token; // For API auth
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vespid_webview);
String partnerParam = getIntent().getStringExtra("partnerParam");
token = getIntent().getStringExtra("token"); // Optional for API auth
webView = findViewById(R.id.webView);
setupWebView();
webView.loadUrl(VESPID_URL + "?partner=" + partnerParam);
}
private void setupWebView() {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
webView.addJavascriptInterface(new WebAppInterface(), "Android");
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
// Listen for token requests from Vespid
injectMessageListener();
}
});
}
private void injectMessageListener() {
String js = "window.addEventListener('message', function(e) {" +
"if (e.data && e.data.type === 'REQUEST_AUTH_TOKEN') {" +
"Android.onTokenRequested();" +
"}" +
"});";
webView.evaluateJavascript(js, null);
}
private void sendToken() {
if (token == null) return;
String js = String.format(
"window.postMessage({type:'AUTH_TOKEN',payload:{token:'%s'}},'*');",
token
);
webView.evaluateJavascript(js, null);
}
private class WebAppInterface {
@JavascriptInterface
public void onTokenRequested() {
runOnUiThread(() -> sendToken());
}
}
}
Usage:
// Native Auth
Intent intent = new Intent(this, VespidWebViewActivity.class);
intent.putExtra("partnerParam", "your_company");
startActivity(intent);
// API Auth
Intent intent = new Intent(this, VespidWebViewActivity.class);
intent.putExtra("partnerParam", "your_company");
intent.putExtra("token", ssoToken);
startActivity(intent);
import UIKit
import WebKit
class VespidWebViewController: UIViewController, WKScriptMessageHandler {
private static let vespidURL = "https://integration.vespid.my"
private var webView: WKWebView!
var partnerParam: String = ""
var token: String? // For API auth
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
loadVespid()
}
private func setupWebView() {
let config = WKWebViewConfiguration()
let contentController = WKUserContentController()
// Listen for messages from Vespid
contentController.add(self, name: "vespidHandler")
// Inject message bridge
let script = WKUserScript(
source: """
window.addEventListener('message', function(e) {
if (e.data && e.data.type === 'REQUEST_AUTH_TOKEN') {
window.webkit.messageHandlers.vespidHandler.postMessage('REQUEST_AUTH_TOKEN');
}
});
""",
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
contentController.addUserScript(script)
config.userContentController = contentController
webView = WKWebView(frame: view.bounds, configuration: config)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
}
private func loadVespid() {
guard let url = URL(string: "\(Self.vespidURL)?partner=\(partnerParam)") else { return }
webView.load(URLRequest(url: url))
}
private func sendToken() {
guard let token = token else { return }
let js = "window.postMessage({type:'AUTH_TOKEN',payload:{token:'\(token)'}},'*');"
webView.evaluateJavaScript(js)
}
// MARK: - WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
if message.body as? String == "REQUEST_AUTH_TOKEN" {
sendToken()
}
}
}
Usage:
// Native Auth
let vc = VespidWebViewController()
vc.partnerParam = "your_company"
navigationController?.pushViewController(vc, animated: true)
// API Auth
let vc = VespidWebViewController()
vc.partnerParam = "your_company"
vc.token = ssoToken
navigationController?.pushViewController(vc, animated: true)
Backend Proxy (Recommended)
Never expose your API key in client code.
- JavaScript (Express)
- Python (Flask)
app.post('/api/vespid/token', async (req, res) => {
const response = await fetch(
'https://api.vespid.my/auth/partners/sso/token',
{
method: 'POST',
headers: {
'X-API-Key': process.env.VESPID_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: req.body.email }),
}
);
const data = await response.json();
res.json(data.data);
});
import os
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/vespid/token', methods=['POST'])
def get_vespid_token():
response = requests.post(
'https://api.vespid.my/auth/partners/sso/token',
headers={
'X-API-Key': os.environ.get('VESPID_API_KEY'),
'Content-Type': 'application/json',
},
json={'email': request.json.get('email')}
)
data = response.json()
return jsonify(data.get('data'))