Skip to main content

Overview

The Lula Direct SDK provides everything you need to embed a branded shopping experience directly into your mobile or web applications. Built for React Native, iOS (Swift), and Android (Kotlin), the SDK offers complete control over your digital storefront while leveraging Lula’s powerful commerce platform.
The SDK handles authentication, deep linking, order processing, and native payment integration automatically - allowing you to launch a branded ordering experience in under a week.

When to Use Lula Direct SDK

Mobile App Integration

Native mobile experiences
  • Embed storefront in React Native apps
  • Native iOS (Swift) integration
  • Native Android (Kotlin) integration
  • Seamless in-app shopping

Rapid Development

Pre-built components
  • Complete WebView-based storefront
  • Automatic JWT authentication
  • Ready-to-use order flows
  • Launch in under a week

Branded Experience

White-label control
  • Your app, your brand
  • Custom WebView configuration
  • Deep linking to products
  • Native payment integration

Simplified Integration

Minimal code required
  • Single component implementation
  • Handles authentication automatically
  • Built-in order management
  • Real-time inventory sync

Getting Started

Follow these steps to integrate Lula Direct SDK into your application:
1

Install Dependencies

Add the SDK and required packages
npm install @lula/direct-sdk react-native-webview @react-native-async-storage/async-storage expo-jwt
For native iOS/Android integration, you’ll also need:
  • iOS: JWTKit library
  • Android: Auth0 JWT library
2

Configure Credentials

Provide your application credentialsRequired:
  • secretKey - JWT signing key (≥32 characters)
  • appBundleId - Your app’s bundle identifier
  • memberId - Your Lula member ID
Store your secret key securely. Never commit it to version control.
3

Initialize SDK

Configure the SDK when your app starts
import { initLulaSDK } from "@lula/direct-sdk";

initLulaSDK({
  secretKey: process.env.LULA_SECRET_KEY,
  appBundleId: "com.yourcompany.app"
}, true);
Store secret keys securely using environment variables
4

Embed Storefront

Add LulaDirectView component to your app
import { LulaDirectView } from "@lula/direct-sdk";

function StoreScreen() {
  return (
    <LulaDirectView
      memberId="YOUR_MEMBER_ID"
      storeId="STORE_123"
      customerInfo={{
        name: "Jane Doe",
        email: "[email protected]",
        age21: true
      }}
      onOrderComplete={(order) => {
        console.log("Order:", order);
        // Navigate to confirmation
      }}
      enableApplePay={true}
      enableGooglePay={true}
    />
  );
}
5

Test & Deploy

Validate your integration
  • Test order flows end-to-end
  • Verify payment methods (Apple Pay/Google Pay)
  • Test deep linking to products/categories
  • Validate error handling
  • Deploy to production
Use development environment for testing before production deployment

Core Concepts

How It Works

The Lula Direct SDK uses a WebView-based approach to embed the Lula storefront into your native application. Here’s how the key components work together:
1

JWT Authentication

The SDK generates secure JWT tokens using your secretKey and appBundleId. These tokens authenticate API requests and contain customer information for personalized experiences.
2

WebView Integration

LulaDirectView renders the Lula storefront in a React Native WebView with automatic authentication, deep linking support, and bi-directional message passing.
3

Token Caching

JWT tokens are cached in AsyncStorage for 24 hours (configurable) to minimize regeneration and improve performance. When a cached token expires, the SDK automatically generates a new token and caches it.
4

Event Communication

The WebView sends events (order completion, missing data) to your app via postMessage, triggering your callback functions.

Key Features

WebView Integration

Seamless embedding
  • React Native WebView component
  • Automatic authentication
  • Deep linking support
  • Native payment methods

JWT Authentication

Secure token-based auth
  • Automatic token generation
  • Token caching with expiration
  • HS256 signing algorithm
  • 24-hour default expiry

Order Management

Complete order handling
  • Order completion callbacks
  • Real-time status updates
  • Cart modification support
  • Order history access

Native Payments

Platform-specific payments
  • Apple Pay (iOS)
  • Google Pay (Android)
  • Secure payment processing
  • PCI compliance built-in

API Reference

Component Props

LulaDirectView

The main React Native component for embedding the Lula Direct storefront. It handles WebView initialization, JWT authentication, URL construction, and message passing between the native app and the web storefront.
PropTypeRequiredDefaultDescription
memberIdstringYes-Your Lula member ID for authentication. Used in URL construction and JWT payload.
storeIdstringNo-Specific store ID to display. If omitted, the storefront may show all stores for the member or a default store.
hideNavbooleanNotrueControls visibility of the Lula navigation bar in the embedded storefront.
customerInfoobjectNo{}Customer details used for JWT authentication and pre-filling checkout information. See CustomerInfo interface below.
deepLinkPathstringNo-Path to navigate to within the storefront (e.g., /product/123, /category/beer). Encoded as both a JWT claim and URL parameter.
onOrderCompletefunctionNo-Callback fired when an order is successfully completed. Receives order data parsed from WebView message with type ORDER_COMPLETE.
onMissingDatafunctionNo-Callback fired when the storefront requires additional customer information. Receives field names from WebView message with type MISSING_DATA.
enableApplePaybooleanNofalseEnables Apple Pay support on iOS. Sets allowsBackForwardNavigationGestures to true.
enableGooglePaybooleanNofalseEnables Google Pay support on Android. Reserved for future Google Pay-specific WebView settings.
sdkConfigobjectNo-Partial SDK configuration to override global settings for this instance. See LulaSDKConfig below.
storeUrlstringNo-Deprecated. Base URL override for the Lula service. Use sdkConfig.baseUrl instead.

CustomerInfo Interface

Customer information object passed to LulaDirectView. All fields are optional and used to pre-populate the storefront and generate the JWT token.
name
string
Customer’s full name. Included in JWT payload as member.name for pre-filling checkout forms.
email
string
Customer’s email address. Included in JWT payload as member.email for account identification and order confirmations.
phone
string
Customer’s phone number. Included in JWT payload as member.phone for delivery updates and customer service.
address
object
Customer’s address object. Included in JWT payload as member.address for delivery and billing purposes.
age21
boolean
Age verification flag indicating if customer is 21 years or older. Required for purchasing restricted products like alcohol. Included in JWT payload as member.age21.
Setting this to false or omitting it may prevent purchase of age-restricted items
interface CustomerInfo {
  name?: string;
  email?: string;
  phone?: string;
  address?: {
    line1?: string;
    line2?: string;
    city?: string;
    state?: string;
    postalCode?: string;
    country?: string;
  };
  age21?: boolean;
}

WebView Message Events

The component listens for messages from the WebView and triggers callbacks based on the message type:
ORDER_COMPLETE
event
Fired when: An order is successfully completed in the storefrontCallback: onOrderComplete(order)Payload: Order object containing order details (structure determined by Lula backend)
onOrderComplete={(order) => {
  console.log("Order ID:", order.id);
  console.log("Total:", order.total);
  // Navigate to confirmation screen
  navigation.navigate('OrderConfirmation', { orderId: order.id });
}}
MISSING_DATA
event
Fired when: The storefront requires additional customer informationCallback: onMissingData(fields)Payload: Array or object containing field names that need to be provided
onMissingData={(fields) => {
  console.log("Required fields:", fields);
  // Show form to collect missing information
  setMissingFields(fields);
  setShowDataModal(true);
}}

SDK Configuration

initLulaSDK(config, isProd?)

Initialize the SDK with configuration options. This should be called once when your application starts, before using LulaDirectView. Parameters:
  • config (Partial<LulaSDKConfig>) - Configuration object with properties to override
  • isProd (boolean, optional) - If true, uses production defaults; otherwise uses development defaults
Returns: Complete LulaSDKConfig object with merged configuration
baseUrl
string
required
Base URL for the Lula Direct service. All storefront URLs are constructed by appending paths to this base.Default Values:
  • Development: https://dev.lulacommerce.com
  • Production: https://client.lulacommerce.com
Can be overridden for local development (e.g., http://localhost:8000)
secretKey
string
required
Secret key used for JWT signing with HS256 algorithm. Must be at least 32 characters long.Security Requirements:
  • Store securely using environment variables
  • Never hardcode in client-side code for production
  • Consider backend JWT generation for sensitive applications
Default Values:
  • Development: dev-secret-key-at-least-32-characters-long
  • Production: Must be provided (no default)
Production apps must provide a secure secret key
appBundleId
string
required
Your application’s bundle identifier (iOS) or package name (Android). Used as the JWT iss (issuer) claim.Examples:
  • iOS: com.yourcompany.yourapp
  • Android: com.yourcompany.yourapp
Default Values:
  • Development: com.yourapp.dev
  • Production: Must be provided (no default)
jwtExpiry
number
JWT token expiration time in minutes. Tokens are cached and reused until expiration.Default: 1440 (24 hours)Considerations:
  • Longer expiry = fewer token regenerations but potential security concerns
  • Shorter expiry = more frequent regenerations but better security
  • Cached tokens are automatically cleared on user logout via clearLulaJWTCache()
defaultPath
string
Default deep link path used when no deepLinkPath is specified in generateLulaJWT().Default: /store/defaultExamples:
  • /store/123
  • /category/featured
  • /product/new-arrivals
import { initLulaSDK } from "@lula/direct-sdk";

initLulaSDK({
  secretKey: "your-dev-secret-key-at-least-32-chars",
  appBundleId: "com.yourcompany.app.dev",
  baseUrl: "http://localhost:3000", // Optional: for local testing
});

Utility Functions

generateLulaJWT(config)

Generates a JWT token for Lula Direct authentication using the HS256 algorithm. The token is automatically cached in AsyncStorage and reused until expiration. Parameters: LulaJWTConfig object Returns: Promise<string> - The JWT token Behavior:
  1. Checks AsyncStorage for a valid cached token
  2. If cached token exists and hasn’t expired, returns it immediately
  3. Otherwise, generates a new token with the current timestamp
  4. Caches the new token with its expiration timestamp
  5. Returns the token
JWT Payload Structure:
{
  "iss": "com.yourapp.bundle",     // From sdkConfig.appBundleId
  "aud": "lula-direct",             // Fixed audience
  "exp": 1234567890,                // Expiration timestamp
  "iat": 1234567890,                // Issued at timestamp
  "member": {
    "id": "memberId",               // From config.memberId
    "name": "John Doe",             // From config.userName
    "email": "[email protected]",    // From config.userEmail
    "phone": "5551234567",          // From config.userPhone
    "address": {...},               // From config.userAddress
    "age21": true                   // From config.isAdult
  },
  "hideNav": true,                  // From config.hideNav
  "path": "/category/beer"          // From config.deepLinkPath
}
memberId
string
Member ID for authentication. If not provided, defaults to "user123" (not recommended for production).
userName
string
User’s display name. Included in JWT member.name claim.
userEmail
string
User’s email address. Included in JWT member.email claim.
userPhone
string
User’s phone number. Included in JWT member.phone claim.
userAddress
object
User’s address object. Included in JWT member.address claim. Structure is flexible.
isAdult
boolean
default:"false"
Age verification flag. Included in JWT member.age21 claim.
hideNav
boolean
default:"true"
Whether to hide navigation in the storefront. Included in JWT hideNav claim.
Path to navigate to. Included in JWT path claim. Falls back to sdkConfig.defaultPath if not provided.
import { generateLulaJWT } from "@lula/direct-sdk";

const jwt = await generateLulaJWT({
  memberId: "YOUR_MEMBER_ID",
  userName: "Jane Doe",
  userEmail: "[email protected]",
  isAdult: true
});

getLulaUrl(path)

Constructs a full URL by combining the configured baseUrl with the provided path. Automatically handles path formatting. Parameters:
  • path (string) - Relative path (e.g., /product/123)
Returns: string - Complete URL Behavior:
  • Ensures path starts with /
  • Handles company-specific subdomains (.lulacommerce.com)
  • Works with custom development URLs
import { getLulaUrl } from "@lula/direct-sdk";

// With production config (baseUrl: https://client.lulacommerce.com)
const url = getLulaUrl("/product/XYZ789");
// Returns: "https://client.lulacommerce.com/product/XYZ789"

// With custom config (baseUrl: http://localhost:3000)
const localUrl = getLulaUrl("store/123");
// Returns: "http://localhost:3000/store/123"

clearLulaJWTCache()

Clears the cached JWT token and its expiration timestamp from AsyncStorage. Should be called when the user logs out or switches accounts to ensure a fresh token is generated on next login. Parameters: None Returns: Promise<void> Storage Keys Cleared:
  • lula_jwt_token - The cached JWT token string
  • lula_jwt_expiry - The token expiration timestamp
import { clearLulaJWTCache } from "@lula/direct-sdk";

async function handleLogout() {
  // Clear user session
  await clearUserSession();

  // Clear Lula JWT cache
  await clearLulaJWTCache();

  // Navigate to login
  navigation.navigate('Login');
}

Advanced Usage

Manual URL Generation

For custom WebView setups or external browser opening:
import { generateLulaJWT, getLulaUrl } from "@lula/direct-sdk";

async function getAuthenticatedStoreUrl() {
  const jwt = await generateLulaJWT({
    memberId: "YOUR_MEMBER_ID",
    userName: "Jane Doe",
    userEmail: "[email protected]",
    deepLinkPath: "/category/beer"
  });

  const storePath = `/us/q?memberID=YOUR_MEMBER_ID&storeID=STORE_123&hideNav=true&token=${jwt}`;
  const fullUrl = getLulaUrl(storePath);

  return fullUrl;
}
The JWT token is appended as a token query parameter for authentication.

Native Platform Integration

iOS (Swift) Implementation

Configuration

struct LulaConfig {
    static let BASE_URL = "https://client.lulacommerce.com"
    static let SECRET_KEY = "YOUR_SECRET_KEY"
    static let APP_BUNDLE_ID = "com.yourcompany.yourapp"
    static let JWT_EXPIRY = 1440 // 24 hours in minutes
}

JWT Generation

import JWTKit

func generateLulaJWT(
    memberId: String,
    customerInfo: [String: Any],
    deepLinkPath: String? = nil
) throws -> String {
    let now = Date()
    let expiryDate = Calendar.current.date(
        byAdding: .minute,
        value: LulaConfig.JWT_EXPIRY,
        to: now
    ) ?? now

    let payload = LulaJWTPayload(
        iss: IssuerClaim(value: LulaConfig.APP_BUNDLE_ID),
        aud: AudienceClaim(value: "lula-direct"),
        exp: ExpirationClaim(value: expiryDate),
        iat: IssuedAtClaim(value: now),
        member: MemberClaim(
            id: memberId,
            name: customerInfo["name"] as? String,
            email: customerInfo["email"] as? String,
            phone: customerInfo["phone"] as? String,
            age21: customerInfo["age21"] as? Bool ?? false
        ),
        hideNav: true,
        path: deepLinkPath ?? "/store/default"
    )

    let signers = JWTSigners()
    signers.use(.hs256(key: LulaConfig.SECRET_KEY.data(using: .utf8)!))
    return try signers.sign(payload)
}

WebView Integration

import WebKit

class LulaWebViewController: UIViewController, WKScriptMessageHandler {
    private var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = WKWebViewConfiguration()
        let contentController = WKUserContentController()
        contentController.add(self, name: "lulaBridge")
        config.userContentController = contentController

        webView = WKWebView(frame: view.bounds, configuration: config)
        view.addSubview(webView)

        do {
            let jwt = try generateLulaJWT(
                memberId: "YOUR_MEMBER_ID",
                customerInfo: [
                    "name": "John Doe",
                    "email": "[email protected]",
                    "age21": true
                ]
            )
            let url = "\(LulaConfig.BASE_URL)/us/q?memberID=YOUR_MEMBER_ID&hideNav=true&token=\(jwt)"
            webView.load(URLRequest(url: URL(string: url)!))
        } catch {
            print("Error generating JWT: \(error)")
        }
    }

    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: Any],
              let type = body["type"] as? String else { return }

        switch type {
        case "orderComplete":
            if let order = body["order"] as? [String: Any] {
                print("Order completed: \(order)")
            }
        case "missingData":
            if let fields = body["fields"] as? [String] {
                print("Missing data: \(fields)")
            }
        default:
            break
        }
    }
}

Android (Kotlin) Implementation

Configuration

object LulaConfig {
    const val BASE_URL = "https://client.lulacommerce.com"
    const val SECRET_KEY = "YOUR_SECRET_KEY"
    const val APP_BUNDLE_ID = "com.yourcompany.yourapp"
    const val JWT_EXPIRY = 1440 // 24 hours in minutes
}

JWT Generation

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import java.util.Date

fun generateLulaJWT(
    memberId: String,
    customerInfo: Map<String, Any>,
    deepLinkPath: String? = null
): String {
    val nowMillis = System.currentTimeMillis()
    val expiryMillis = nowMillis + (LulaConfig.JWT_EXPIRY * 60 * 1000)

    val algorithm = Algorithm.HMAC256(LulaConfig.SECRET_KEY)

    val memberClaim = mapOf(
        "id" to memberId,
        "name" to (customerInfo["name"] as? String),
        "email" to (customerInfo["email"] as? String),
        "phone" to (customerInfo["phone"] as? String),
        "age21" to (customerInfo["age21"] as? Boolean ?: false)
    )

    return JWT.create()
        .withIssuer(LulaConfig.APP_BUNDLE_ID)
        .withAudience("lula-direct")
        .withIssuedAt(Date(nowMillis))
        .withExpiresAt(Date(expiryMillis))
        .withClaim("member", memberClaim)
        .withClaim("hideNav", true)
        .withClaim("path", deepLinkPath ?: "/store/default")
        .sign(algorithm)
}

WebView Integration

import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class LulaWebViewActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val webView = WebView(this)
        setContentView(webView)

        webView.settings.apply {
            javaScriptEnabled = true
            domStorageEnabled = true
        }
        webView.webViewClient = WebViewClient()
        webView.addJavascriptInterface(LulaBridge(), "lulaBridge")

        val jwt = generateLulaJWT(
            memberId = "YOUR_MEMBER_ID",
            customerInfo = mapOf(
                "name" to "John Doe",
                "email" to "[email protected]",
                "age21" to true
            )
        )
        val url = "${LulaConfig.BASE_URL}/us/q?memberID=YOUR_MEMBER_ID&hideNav=true&token=$jwt"
        webView.loadUrl(url)
    }

    inner class LulaBridge {
        @JavascriptInterface
        fun postMessage(json: String) {
            // Parse JSON and handle orderComplete/missingData events
        }
    }
}

Security Considerations

Important Security Guidelines

Secret Key Management

  1. Never hardcode production keys in client-side code
  2. Use environment variables for configuration
  3. Consider backend JWT generation for production apps
  4. Use secure storage:
    • iOS: Keychain
    • Android: EncryptedSharedPreferences
    • React Native: react-native-encrypted-storage

WebView Security

// WebView security settings
<WebView
  javaScriptEnabled={true}
  domStorageEnabled={true}
  allowsInlineMediaPlayback={true}
  mediaPlaybackRequiresUserAction={false}
  onError={(syntheticEvent) => {
    const { nativeEvent } = syntheticEvent;
    console.error('WebView error: ', nativeEvent);
  }}
/>

JWT Best Practices

  • Monitor token expiration (default: 24 hours)
  • Clear tokens on user logout
  • Implement token refresh mechanisms
  • Validate token structure and claims

Troubleshooting

Common Issues

Possible causes and solutions:
  • Verify react-native-webview is correctly installed and linked
  • Check baseUrl in initLulaSDK is correct and reachable
  • Ensure memberId is provided in initLulaSDK or LulaDirectView
  • Check console for errors during initialization or JWT generation
Token validation issues:
  • Verify secretKey matches your Lula Direct account
  • Ensure appBundleId is correctly configured
  • Confirm secretKey is at least 32 characters long
  • Check JWT expiration (default: 24 hours)
  • Call clearLulaJWTCache() to refresh token
Navigation problems:
  • Ensure deepLinkPath is a valid Lula Direct path:
    • /store/STORE_ID
    • /product/PRODUCT_ID
    • /category/CATEGORY_ID
  • Verify JWT generation includes the deepLinkPath
  • Check path format matches Lula’s routing structure
Insufficient customer information:
  • Provide complete customerInfo in LulaDirectView
  • Include required fields: name, email, phone
  • Add age21 verification for restricted products
  • Check onMissingData callback for specific requirements
Apple Pay / Google Pay issues:
  • Verify enableApplePay is set on iOS devices
  • Verify enableGooglePay is set on Android devices
  • Check device supports the payment method
  • Ensure payment credentials are configured in Lula

Native Integration Issues

WebView not loading:
  • Verify generated URL is correct
  • Check network connectivity and SSL certificates
  • Enable required WebView settings
  • Review WKWebView console logs
JWT generation errors:
  • Verify JWTKit library is installed
  • Check secret key length (≥32 characters)
  • Ensure proper date handling for expiration
  • Validate payload structure
WebView not loading:
  • Enable JavaScript and DOM storage
  • Check network permissions in manifest
  • Verify URL format and accessibility
  • Review WebView client logs
JWT generation errors:
  • Verify Auth0 JWT library dependency
  • Check algorithm configuration (HS256)
  • Ensure proper time calculation
  • Validate claim structure

Additional Resources

Support

Need help? Contact your Lula representative for:
  • SDK access and credentials
  • Technical implementation support
  • Production deployment coordination
  • Troubleshooting assistance
The SDK is licensed under the MIT License.
I