Developers

Authentication

Verify platform tokens, implement OAuth 2.0, and secure your plugin endpoints.

Overview

Every request from the platform to your plugin includes authentication information. This page covers how to verify those requests and how to set up OAuth if your plugin needs to access external user accounts.

Platform tokens

Every tool call includes a signed token in the Authorization header:

typescript
Authorization: Bearer {token}

The token is a signed payload containing information about the request.

Token structure

The token is a base64url-encoded payload followed by a dot and a base64url-encoded HMAC-SHA256 signature:

typescript
{base64url_payload}.{base64url_signature}

The decoded payload contains:

json
{
  "serviceName": "MY_PLUGIN",
  "organizationId": "org_abc123",
  "instanceId": "inst_xyz789",
  "toolName": "lookup_customer",
  "issuedAt": 1700000000000,
  "expiresAt": 1700000300000
}

Verifying the token

To verify a platform token:

  1. Split the token on the . separator to get the payload and signature
  2. Base64url-decode the payload
  3. Compute HMAC-SHA256 of the payload using your plugin's secret as the key
  4. Compare your computed signature with the signature from the token
  5. Check that expiresAt is in the future (tokens are valid for 5 minutes)

Here's an example in Node.js:

javascript
const crypto = require('crypto');
 
function verifyPlatformToken(token, secret) {
  const [payloadB64, signatureB64] = token.split('.');
 
  // Compute expected signature
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payloadB64)
    .digest('base64url');
 
  // Compare signatures (timing-safe)
  if (!crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(signatureB64)
  )) {
    throw new Error('Invalid signature');
  }
 
  // Decode and check expiration
  const payload = JSON.parse(
    Buffer.from(payloadB64, 'base64url').toString()
  );
 
  if (Date.now() > payload.expiresAt) {
    throw new Error('Token expired');
  }
 
  return payload;
}

Your plugin secret

When you install a plugin in sandbox mode, the platform generates a secret and shows it once. Save this secret securely — you'll use it to verify platform tokens.

If you lose your secret, you can rotate it from the Plugin Dev Portal.

User identity

For privacy, the platform never sends the customer's actual WhatsApp phone number to your plugin. Instead, it sends a hashed identifier:

json
{
  "user": {
    "id": "a1b2c3d4e5...",
    "hashVersion": 1
  }
}

The id is a deterministic HMAC-SHA256 hash of the user's WhatsApp JID. This means:

  • The same user always produces the same hash
  • You can track user-specific data without knowing their phone number
  • Different organizations produce different hashes for the same user

OAuth 2.0

If your plugin needs to access user accounts on external services (e.g., a CRM, email provider, or project management tool), you can implement OAuth 2.0.

How it works

  1. Your manifest declares auth.type: "oauth2" with the provider's authorization and token URLs
  2. The organization admin configures OAuth credentials (Client ID and Client Secret) for your plugin
  3. When the agent tries to use a tool that requires OAuth and the user hasn't authorized yet, the platform generates an authorization URL and sends it to the user via WhatsApp
  4. The user clicks the link, authorizes on the external service, and is redirected back to the platform
  5. The platform exchanges the authorization code for tokens, encrypts them, and stores them
  6. Future tool calls include the user's access token in the request

Setting up OAuth credentials

After installing a plugin that uses OAuth:

  1. Go to the Plugin Dev Portal or Integrations page
  2. Find the plugin and click Configure
  3. Enter:
    • Client ID — From the external service's OAuth settings
    • Client Secret — From the external service's OAuth settings
    • Authorization URL — Where users are sent to authorize (from your manifest)
    • Token URL — Where the platform exchanges codes for tokens (from your manifest)
    • Scopes — What access levels to request
  4. Save the configuration

Receiving the access token

When a user has authorized, their access token is included in tool call requests in two places:

  1. In the X-User-Access-Token header
  2. In the context.userAccessToken field of the request body
json
{
  "tool": "create_task",
  "input": { "title": "Follow up with customer" },
  "context": {
    "organizationId": "org_abc123",
    "instanceId": "inst_xyz789",
    "userAccessToken": "eyJhbGciOiJSUzI1NiIs..."
  }
}

Use this token to make authenticated requests to the external service on behalf of the user.

Token refresh

The platform automatically refreshes OAuth tokens before they expire (with a 5-minute buffer). Your plugin doesn't need to handle token refresh — just use whatever token is provided in the request.

If a refresh fails, the platform will prompt the user to re-authorize.

When no token exists

If the agent calls a tool that requires OAuth and the user hasn't authorized yet, the platform:

  1. Stops the tool call
  2. Sends the user a WhatsApp message with an authorization link
  3. The user authorizes in their browser
  4. The next time the agent tries to call the tool, the token will be available

Your endpoint won't receive the request until authorization is complete — you don't need to handle the "no token" case.

Best practices

Always verify platform tokens

Even if your plugin doesn't handle sensitive data, verify the token signature on every request. This prevents unauthorized callers from invoking your tools.

Store secrets securely

Keep your plugin secret in environment variables or a secrets manager. Never commit it to version control or expose it in client-side code.

Use HTTPS

Always serve your plugin over HTTPS. The platform will send tokens and potentially OAuth credentials to your endpoint.

Handle errors gracefully

If authentication fails, return a clear error message:

json
{
  "error": "Authentication failed",
  "message": "Invalid or expired platform token"
}

Next steps