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

> Discover how WeKnora embed widget secure-mode handles token exchange and rate limiting to protect your application. Learn about publish and session tokens.

- Repository: [Tencent/WeKnora](https://github.com/tencent/WeKnora)
- Tags: how-to-guide
- Published: 2026-09-12

---

**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:
    ```http
    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:
    ```json
    {
      "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`](https://github.com/Tencent/WeKnora/blob/main/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`](https://github.com/Tencent/WeKnora/blob/main/docs/embed-secure-mode.md) (lines 84-108), implement the exchange endpoint:

```javascript
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`](https://github.com/Tencent/WeKnora/blob/main/docs/embed-secure-mode.md) (lines 111-143):

```go
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:

```html
<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`](https://github.com/Tencent/WeKnora/blob/main/frontend/public/weknora-widget.js) SDK reads `data-token-endpoint`, fetches the JSON payload, and manages token refresh automatically.

## Key Source Files

- [`docs/embed-secure-mode.md`](https://github.com/Tencent/WeKnora/blob/main/docs/embed-secure-mode.md): Complete workflow documentation, Node.js and Go examples (lines 41-143)
- [`docs/embed-subdomain.md`](https://github.com/Tencent/WeKnora/blob/main/docs/embed-subdomain.md): Subdomain configuration affecting origin whitelist validation
- [`frontend/public/weknora-widget.js`](https://github.com/Tencent/WeKnora/blob/main/frontend/public/weknora-widget.js): Client-side SDK handling token acquisition and refresh
- [`internal/server/embed.go`](https://github.com/Tencent/WeKnora/blob/main/internal/server/embed.go): Server-side exchange handler implementing token validation and rate limiting
- [`backend/api/v1/embed/channel.go`](https://github.com/Tencent/WeKnora/blob/main/backend/api/v1/embed/channel.go): Channel data model including `RateLimitPerMinute` and `RateLimitPerDay` fields
- [`client/src/api/embed/index.ts`](https://github.com/Tencent/WeKnora/blob/main/client/src/api/embed/index.ts): TypeScript definitions for the exchange API

## 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`](https://github.com/Tencent/WeKnora/blob/main/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.