API Client

Session Tokens

How to use short-lived session tokens to authenticate plugin frontend requests to your backend server.

Session tokens are short-lived JWTs (5-minute expiry) that prove a request originates from an authenticated user within the Whatalo admin. They are the bridge between your plugin's frontend (iframe) and your backend server.

Session tokens do not grant access to the Whatalo API directly. Their purpose is to let your backend know that the incoming request is legitimate before it uses your API key to fetch data on behalf of that user.

Getting a Session Token

In your plugin frontend, request a token from the App Bridge:

import { sessionToken } from "@whatalo/plugin-sdk/bridge";

// Returns a token and its expiration time
const { token, expiresAt } = await sessionToken();

// Send it as a Bearer token to your backend
const response = await fetch("/api/orders", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

const data = await response.json();

The App Bridge caches the token and returns the cached copy on subsequent calls. When the token is within 60 seconds of expiry, the next call to sessionToken() fetches a new one transparently — you do not need to manage this yourself.

Verifying on Your Backend

Use verifyWhataloSessionToken from @whatalo/plugin-sdk/server to validate the token before trusting the request:

import { verifyWhataloSessionToken } from "@whatalo/plugin-sdk/server";
import { WhataloClient } from "@whatalo/plugin-sdk";

export async function GET(request: Request) {
  const authHeader = request.headers.get("Authorization") ?? "";
  const token = authHeader.replace("Bearer ", "");

  if (!token) {
    return new Response("Missing authorization header", { status: 401 });
  }

  // Verify the token — throws if invalid or expired
  let claims;
  try {
    claims = verifyWhataloSessionToken(token, process.env.WHATALO_CLIENT_SECRET!);
  } catch (error) {
    return new Response("Invalid or expired session token", { status: 401 });
  }

  // Token is valid — use your API key to call the Whatalo API
  const client = new WhataloClient({ apiKey: process.env.WHATALO_API_KEY! });
  const orders = await client.orders.list({ page: 1, per_page: 25 });

  return Response.json(orders);
}

verifyWhataloSessionToken throws an Error if the token is expired, the signature does not match, or the token structure is invalid. Catch it with a standard try/catch block.

Token Claims

The decoded token contains the following claims:

ClaimTypeDescription
iss"whatalo"Token issuer — always "whatalo"
audstringYour plugin's client ID
substringStore ID (same as storeId)
expnumberExpiration timestamp (Unix, UTC)
iatnumberIssued-at timestamp (Unix, UTC)
jtistringUnique token ID — prevents replay attacks
storeIdstringPublic store identifier
appIdstringYour plugin's public ID
scopesstring[]Scopes granted by the merchant at install time
installationIdstringInstallation record ID

Checking Scopes

If your endpoint requires a specific permission scope, check it after verifying the token:

const claims = verifyWhataloSessionToken(token, process.env.WHATALO_CLIENT_SECRET!);

if (!claims.scopes.includes("read:orders")) {
  return new Response("Insufficient permissions", { status: 403 });
}

// Scope confirmed — proceed
const orders = await client.orders.list();

Auto-Refresh

The App Bridge handles token lifecycle automatically:

  • Cache hit: If the cached token has more than 60 seconds remaining, sessionToken() returns it immediately without a network request.
  • Proactive refresh: If the token has 60 seconds or less remaining, a new token is fetched before returning.
  • Concurrent calls: Multiple simultaneous calls to sessionToken() share a single in-flight refresh — only one network request is made.

You do not need to store tokens, track expiry times, or schedule refresh logic. Call sessionToken() before every request to your backend.

During development, whatalo dev tunnels a single app origin. Relative browser calls such as /api/orders stay on that tunneled origin when your frontend and backend share the same local app server.

Full Request Example

A complete frontend-to-backend flow fetching store orders:

import { useEffect, useState } from "react";
import { sessionToken } from "@whatalo/plugin-sdk/bridge";

interface Order {
  id: string;
  status: string;
  total: number;
}

function OrderList() {
  const [orders, setOrders] = useState<Order[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchOrders() {
      try {
        // Always get a fresh (or cached) session token
        const { token } = await sessionToken();

        const res = await fetch("/api/orders", {
          headers: { Authorization: `Bearer ${token}` },
        });

        if (!res.ok) {
          throw new Error(`Request failed: ${res.status}`);
        }

        const data = await res.json();
        setOrders(data);
      } catch (err) {
        setError(err instanceof Error ? err.message : "Failed to load orders");
      } finally {
        setLoading(false);
      }
    }

    fetchOrders();
  }, []);

  if (loading) return <p>Loading orders...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {orders.map((order) => (
        <li key={order.id}>
          {order.id} — {order.status} — ${order.total}
        </li>
      ))}
    </ul>
  );
}

Security Notes

  • Always verify on your backend — never trust the token without calling verifyWhataloSessionToken. The token is signed; an unverified token provides no security guarantee.
  • Never forward the session token to the Whatalo API — it is not accepted as a Whatalo API credential. Use your WHATALO_API_KEY for Whatalo API calls.
  • Use HTTPS in production — session tokens must only be transmitted over encrypted connections.
  • Do not store session tokens server-side — they are short-lived by design. Fetch a new one for each user action.
  • Check the jti claim for sensitive operations — for actions that must not be replayed (e.g., creating an order), record used jti values and reject duplicates.

On this page