How WeKnora Embed Widget Secure-Mode Token Exchange and Rate Limiting Works

WeKnora's embed widget secure-mode exchanges long-lived publish tokens (em_…) for short-lived session tokens (ems_…) via your backend, enforcing per-minute and per-day rate limits on the exchange endpoint to prevent abuse.

The Tencent/WeKnora repository provides an embeddable widget that supports a secure operational mode to prevent token exposure. In this configuration, the embed widget secure-mode token exchange and rate limiting mechanisms work together to ensure the long-lived publish token never reaches the client browser while protecting the API from brute-force attacks.

Secure-Mode Token Exchange Workflow

The token exchange follows a six-step delegation pattern where your backend acts as a trusted intermediary:

  1. Visitor Browser loads the embed widget iframe configured with data-token-endpoint pointing to your backend. Crucially, no data-token attribute is present in the HTML, ensuring the publish token remains server-side.

  2. Your Backend receives the GET request and verifies user authentication via session cookies or JWT. Unauthenticated requests return 401 Unauthorized.

  3. Your Backend calls the WeKnora API endpoint:

    POST /api/v1/embed/<CHANNEL_ID>/exchange

    with headers:

    • Authorization: Embed <publish-token>
    • Origin: https://<your-backend> (must match the channel whitelist)
  4. WeKnora API validates the publish token, Origin header, and rate-limit counters. Successful validation returns:

    {
      "session_token": "ems_...",
      "expires_in": 1800
    }
  5. Your Backend returns the payload { "token": "<ems_…>", "expiresIn": 1800 } to the widget.

  6. Widget stores the session token (valid for ~30 minutes) and uses it for subsequent API calls, automatically refreshing when expired.

Critical Security Requirements

  • Publish Token Isolation: The em_… publish token must remain in the WEKNORA_PUBLISH_TOKEN environment variable on your server. It never appears in browser source code.
  • Explicit Origin Header: When calling the exchange endpoint, you must manually set the Origin header. The server-side fetch does not add it automatically; omission causes a 403 "origin not allowed" error.
  • Domain Whitelist: The channel configuration in WeKnora maintains a whitelist of allowed origins including your embed page domain and the backend exchange domain.

Rate Limiting Architecture

According to the WeKnora source code, the exchange endpoint implements two-tier rate limiting stored in the embed_channels database table.

Per-Minute and Daily Quotas

Every embed channel stores two limit fields:

  • rate_limit_per_minute: Defines the maximum exchange requests allowed in any rolling 60-second window. A token-bucket algorithm tracks usage; exceeding the limit returns 429 Too Many Requests.
  • rate_limit_per_day: Defines the maximum requests allowed per calendar day. The counter resets at midnight UTC, and exceeding it returns 429 until the next day.

Enforcement Logic

These limits apply before token validation in internal/server/embed.go. When either threshold is exceeded, the server immediately returns HTTP 429 without validating the publish token, ensuring abusive clients cannot bypass limits with randomized tokens.

Production Implementation Examples

Node.js Express Token Endpoint

As documented in docs/embed-secure-mode.md (lines 84-108), implement the exchange endpoint:

const WEKNORA_BASE = 'https://<WEKNORA_HOST>';
const CHANNEL_ID = '<CHANNEL_ID>';
const ALLOWED_ORIGIN = 'https://shop.example.com';

app.get('/weknora/embed-token', async (req, res) => {
  // 1. Verify visitor authentication
  const hasSession = Boolean(req.cookies?.session_id);
  const auth = req.headers.authorization || '';
  if (!hasSession && !auth.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'unauthorized' });
  }

  // 2. Exchange publish token for session token
  const r = await fetch(`${WEKNORA_BASE}/api/v1/embed/${CHANNEL_ID}/exchange`, {
    method: 'POST',
    headers: {
      Authorization: 'Embed ' + process.env.WEKNORA_PUBLISH_TOKEN,
      Origin: ALLOWED_ORIGIN,
    },
  });
  const body = await r.json();

  // 3. Return session token to widget
  if (!body?.data?.session_token) {
    return res.status(502).json({ error: 'mint failed' });
  }
  res.json({ 
    token: body.data.session_token, 
    expiresIn: body.data.expires_in 
  });
});

Go HTTP Handler

The equivalent Go implementation from docs/embed-secure-mode.md (lines 111-143):

func embedTokenHandler(w http.ResponseWriter, r *http.Request) {
    // 1. Authentication check
    if r.Header.Get("Authorization") == "" && r.Header.Get("Cookie") == "" {
        http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
        return
    }

    // 2. Call WeKnora exchange endpoint
    req, _ := http.NewRequest(http.MethodPost,
        "https://<WEKNORA_HOST>/api/v1/embed/<CHANNEL_ID>/exchange", nil)
    req.Header.Set("Authorization", "Embed "+os.Getenv("WEKNORA_PUBLISH_TOKEN"))
    req.Header.Set("Origin", "https://shop.example.com")
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil || resp.StatusCode >= 300 {
        http.Error(w, `{"error":"mint failed"}`, http.StatusBadGateway)
        return
    }
    defer resp.Body.Close()

    // 3. Extract and return token
    var body struct {
        Data struct {
            SessionToken string `json:"session_token"`
            ExpiresIn    int    `json:"expires_in"`
        } `json:"data"`
    }
    if json.NewDecoder(resp.Body).Decode(&body) != nil || body.Data.SessionToken == "" {
        http.Error(w, `{"error":"mint failed"}`, http.StatusBadGateway)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]any{
        "token": body.Data.SessionToken, 
        "expiresIn": body.Data.ExpiresIn,
    })
}

Frontend Widget Configuration

In your HTML, reference the token endpoint without exposing credentials:

<iframe
  src="https://widget.weknora.cn/embed.html"
  data-token-endpoint="https://shop.example.com/weknora/embed-token"
  data-channel-id="YOUR_CHANNEL_ID">
</iframe>

The frontend/public/weknora-widget.js SDK reads data-token-endpoint, fetches the JSON payload, and manages token refresh automatically.

Key Source Files

Summary

  • WeKnora's embed widget secure-mode prevents token leakage by exchanging long-lived em_… publish tokens for short-lived ems_… session tokens through your backend.
  • The exchange endpoint requires explicit Origin headers and validates against a domain whitelist defined in the channel configuration.
  • Rate limiting operates on two tiers: per-minute (rolling window) and per-day (UTC calendar), enforced via token-bucket algorithms before token validation.
  • Implementation requires server-side handlers in Node.js, Go, or similar to authenticate users and proxy the exchange request with the WEKNORA_PUBLISH_TOKEN environment variable.

Frequently Asked Questions

How long does a session token remain valid?

Session tokens (ems_…) expire after approximately 30 minutes (1800 seconds). The frontend/public/weknora-widget.js SDK automatically detects expiration and re-fetches a fresh token from your configured endpoint without user intervention.

Why do I receive a 403 "origin not allowed" error?

The WeKnora API validates the Origin header against the channel's whitelist configured in the admin UI. You must explicitly set the Origin header in your backend request (it is not automatic), and the value must match an entry in the whitelist. Both your frontend domain and backend exchange domain require whitelist entries.

What happens when rate limits are exceeded?

When the per-minute or per-day thresholds are exceeded, the API returns 429 Too Many Requests. These limits apply before token validation, meaning repeated requests with invalid tokens still count toward your quota. The per-day counter resets at midnight UTC.

Can I use the same publish token for multiple channels?

No. Publish tokens (em_…) are scoped to specific channels identified by CHANNEL_ID. The exchange endpoint URL includes the channel ID (/api/v1/embed/<CHANNEL_ID>/exchange), and the token is validated against that channel's specific configuration, rate limits, and whitelist.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →