WebView

This guide covers how to integrate the Kard Rewards WebView in your mobile or web application.

Introduction

Kard’s Rewards WebView provides a turnkey front-end offers experience, complete with a rewards map and intuitive offer discovery for their users. Issuers can integrate Kard’s WebView seamlessly into their existing experiences allowing them to launch and iterate on their rewards program quickly and easily.

It is designed to be embedded in:

  • Mobile apps via native WebView components (iOS WKWebView, Android WebView, React Native WebView)
  • Web applications via iframe

Environment URLs

EnvironmentURL
Testhttps://webview-test-us-east-1.getkard.com/
Productionhttps://webview-prod-us-east-1.getkard.com/

Query Parameters

token (required)

A JWT authentication token that identifies the user and organization.

Required JWT claims:

ClaimDescription
subThe user ID
issuer_idThe organization ID

The token is used for API authentication when the WebView makes requests to Kard services.

theme (optional)

A base64url-encoded JSON string containing design token overrides for customizing the appearance. Uses RFC4648 base64url encoding which is URL-safe (uses - instead of +, _ instead of /, and omits padding). No encodeURIComponent is needed when using base64url encoding.

Structure:

1{
2 "theme": "system" | "light" | "dark",
3 "styles": {
4 "light": { /* design tokens for light mode */ },
5 "dark": { /* design tokens for dark mode */ },
6 "layout": { /* mode-independent layout tokens (e.g., radius) */ }
7 },
8 "labels": {
9 "rewardsTitle": "Custom rewards page title",
10 "nearbyOffersTitle": "Custom nearby offers title",
11 "offersTitle": "Custom offers page title"
12 }
13}

These tokens are mode-dependent and can differ between light and dark themes:

TokenDescription
backgroundPage background color
primaryPrimary brand color
buttonPrimaryTextColorText on primary buttons
secondarySecondary color
buttonSecondaryTextColorText on secondary buttons
textPrimaryPrimary text color
textSecondarySecondary text color
cardBackgroundColorCard background
borderBorder color
linkColorLink/button text color

All color values must be valid CSS color values (hex, rgb, hsl, oklch, named colors, etc.).

These tokens are mode-independent and apply to both light and dark themes:

TokenDescription
radiusBorder radius (CSS length)
chipRadiusFilter chip border radius (CSS length)
imageRadiusImage container border radius (CSS length)
cardRadiusCard border radius (CSS length)

labels (optional)

The labels property within the theme parameter allows customization of page titles displayed in the WebView. All label properties are optional — if not specified, the default values are used.

LabelDefaultDescription
rewardsTitleRewardsTitle displayed on the rewards page
nearbyOffersTitleNearby offersTitle displayed on the nearby offers page
offersTitleOffersTitle displayed on the offers page

Fetching a WebView JWT Token

The Kard SDK provides methods to generate WebView tokens. The SDK is available in multiple languages:

SDK Method:

1users.auth.getWebViewToken(organizationId, userId)

This returns a signed JWT that should be passed as the token query parameter.

WebView tokens can also be generated via REST API. Refer to the API documentation for Get WebView Token.

Location Message Passing Contract

The WebView uses a message-passing protocol to request location data from the container application. This is required for the rewards map feature to display nearby offers.

Message Types

TypeDirectionDescription
REQUEST_LOCATIONWebView → ContainerWebView requests current location
LOCATION_RESPONSEContainer → WebViewContainer sends location data
ERRORContainer → WebViewContainer reports an error

Request Format (from WebView)

When the WebView needs location data, it sends:

1{
2 "type": "REQUEST_LOCATION"
3}

Success Response Format (to WebView)

When location is successfully retrieved:

1{
2 "type": "LOCATION_RESPONSE",
3 "payload": {
4 "ok": true,
5 "coords": {
6 "latitude": 40.7128,
7 "longitude": -74.0060,
8 "accuracy": 10,
9 "altitude": null,
10 "heading": null,
11 "speed": null
12 },
13 "timestamp": 1706380800000
14 }
15}

Coordinate fields:

FieldTypeDescription
latitudenumberLatitude in degrees
longitudenumberLongitude in degrees
accuracynumber | nullAccuracy in meters
altitudenumber | nullAltitude in meters
headingnumber | nullHeading in degrees
speednumber | nullSpeed in meters/second

Error Response Format (to WebView)

When location cannot be retrieved:

1{
2 "type": "ERROR",
3 "payload": {
4 "ok": false,
5 "error": "Location permission not granted"
6 }
7}

Platform Integration Examples

1import { useCallback, useRef } from 'react';
2import * as Location from 'expo-location';
3import WebView, { ShouldStartLoadRequest } from 'react-native-webview';
4
5const WEBVIEW_URL = 'https://webview-prod-us-east-1.getkard.com/';
6const ALLOWED_ORIGIN = new URL(WEBVIEW_URL).origin;
7
8interface RewardsWebViewProps {
9 token: string;
10 themeOverrides?: {
11 theme?: 'system' | 'light' | 'dark';
12 styles?: {
13 light?: Record<string, string>;
14 dark?: Record<string, string>;
15 };
16 labels?: {
17 rewardsTitle?: string;
18 nearbyOffersTitle?: string;
19 offersTitle?: string;
20 };
21 };
22}
23
24// RFC4648 base64url encoding (URL-safe, no padding)
25function encodeThemeOverrides(overrides: object): string {
26 return btoa(JSON.stringify(overrides))
27 .replace(/\+/g, '-')
28 .replace(/\//g, '_')
29 .replace(/=+$/, '');
30}
31
32export function RewardsWebView({ token, themeOverrides }: RewardsWebViewProps) {
33 const webviewRef = useRef<WebView>(null);
34
35 const handleMessage = useCallback(async (event: any) => {
36 try {
37 const msg = JSON.parse(event?.nativeEvent?.data);
38
39 if (msg?.type === 'REQUEST_LOCATION') {
40 // Request location permission
41 const { status } = await Location.requestForegroundPermissionsAsync();
42
43 if (status !== 'granted') {
44 webviewRef.current?.postMessage(JSON.stringify({
45 type: 'ERROR',
46 payload: { ok: false, error: 'Location permission not granted' }
47 }));
48 return;
49 }
50
51 // Get current position
52 const position = await Location.getCurrentPositionAsync({});
53
54 webviewRef.current?.postMessage(JSON.stringify({
55 type: 'LOCATION_RESPONSE',
56 payload: {
57 ok: true,
58 coords: {
59 latitude: position.coords.latitude,
60 longitude: position.coords.longitude,
61 accuracy: position.coords.accuracy,
62 altitude: position.coords.altitude,
63 heading: position.coords.heading,
64 speed: position.coords.speed,
65 },
66 timestamp: position.timestamp,
67 }
68 }));
69 }
70 } catch (error) {
71 console.error('Error handling WebView message:', error);
72 }
73 }, []);
74
75 // base64url is URL-safe, no encodeURIComponent needed for theme param
76 const themeParam = themeOverrides
77 ? `&theme=${encodeThemeOverrides(themeOverrides)}`
78 : '';
79
80 const handleShouldStartLoadWithRequest = useCallback(
81 (request: ShouldStartLoadRequest) => {
82 try {
83 const url = new URL(request.url);
84 return url.origin === ALLOWED_ORIGIN;
85 } catch {
86 return false;
87 }
88 },
89 [],
90 );
91
92 return (
93 <WebView
94 ref={webviewRef}
95 source={{ uri: `${WEBVIEW_URL}?token=${encodeURIComponent(token)}${themeParam}` }}
96 onMessage={handleMessage}
97 onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
98 originWhitelist={[ALLOWED_ORIGIN]}
99 />
100 );
101}

Theming Resources

Below is an example showing how to apply custom branding:

1// Define your brand colors
2const themeOverrides = {
3 theme: 'light' as const,
4 styles: {
5 light: {
6 primary: '#0066cc',
7 buttonPrimaryTextColor: '#ffffff',
8 secondary: '#e0e0e0',
9 buttonSecondaryTextColor: '#1a1a1a',
10 background: '#f5f5f5',
11 textPrimary: '#1a1a1a',
12 textSecondary: '#6b7280',
13 cardBackgroundColor: '#ffffff',
14 border: '#e0e0e0',
15 linkColor: '#0066cc',
16 },
17 dark: {
18 primary: '#4da6ff',
19 buttonPrimaryTextColor: '#000000',
20 secondary: '#404040',
21 buttonSecondaryTextColor: '#f5f5f5',
22 background: '#1a1a1a',
23 textPrimary: '#f5f5f5',
24 textSecondary: '#a0a0a0',
25 cardBackgroundColor: '#2a2a2a',
26 border: '#404040',
27 linkColor: '#4da6ff',
28 },
29 layout: {
30 radius: '8px',
31 chipRadius: '9999px',
32 imageRadius: '9999px',
33 cardRadius: '12px',
34 }
35 },
36 labels: {
37 rewardsTitle: 'My Rewards',
38 nearbyOffersTitle: 'Deals near you',
39 offersTitle: 'All deals',
40 }
41};
42
43// RFC4648 base64url encoding (URL-safe, no padding)
44function encodeThemeOverrides(overrides: object): string {
45 return btoa(JSON.stringify(overrides))
46 .replace(/\+/g, '-')
47 .replace(/\//g, '_')
48 .replace(/=+$/, '');
49}
50
51// base64url is URL-safe, no encodeURIComponent needed
52const themeParam = encodeThemeOverrides(themeOverrides);
53const url = `https://webview-prod-us-east-1.getkard.com/?token=${token}&theme=${themeParam}`;