Skip to main content
This page assumes you’ve already created a bot in the Zenzap console. If not, start at Setup — that’s where you pick a credential type. Open the tab that matches your bot’s credential type.
Every request carries three headers:
Authorization: Bearer YOUR_API_KEY
X-Signature: <HMAC-SHA256 hex-encoded signature>
X-Timestamp: <Unix timestamp in milliseconds>
The signature payload differs by HTTP method:
  • POST/PUT/PATCH/DELETE: sign {timestamp}.{body}
  • GET: sign {timestamp}.{uri} (full path + query string)
For multipart/form-data requests, sign the exact raw request body bytes with a timestamp prefix: {timestamp}.<raw-bytes>.Requests with timestamps older than 5 minutes are rejected.

How to calculate

  1. Get the current Unix timestamp in milliseconds.
  2. Build the payload:
    • POST/PUT/PATCH/DELETE: {timestamp}.{raw-body}
    • GET: {timestamp}.{uri} (for example /v2/members?limit=10&offset=0)
  3. Calculate HMAC-SHA256 of the payload using your API secret.
  4. Hex-encode the digest (64 lowercase chars).
  5. Send both X-Signature and X-Timestamp.

Example — POST (Python)

import json
import hmac
import hashlib
import time
import requests

def create_topic(name: str, members: list[str]) -> dict:
    body = {"name": name, "members": members}
    body_json = json.dumps(body, separators=(",", ":"))
    timestamp = int(time.time() * 1000)

    signature_payload = f"{timestamp}.{body_json}"
    signature = hmac.new(
        API_SECRET.encode(),
        signature_payload.encode(),
        hashlib.sha256,
    ).hexdigest()

    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "X-Signature": signature,
        "X-Timestamp": str(timestamp),
    }

    response = requests.post(f"{BASE_URL}/v2/topics", headers=headers, data=body_json)
    response.raise_for_status()
    return response.json()

Example — GET (Python)

import hmac
import hashlib
import time
import requests

def get_topic(topic_id: str) -> dict:
    uri_path = f"/v2/topics/{topic_id}"
    timestamp = int(time.time() * 1000)

    signature_payload = f"{timestamp}.{uri_path}"
    signature = hmac.new(
        API_SECRET.encode(),
        signature_payload.encode(),
        hashlib.sha256,
    ).hexdigest()

    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "X-Signature": signature,
        "X-Timestamp": str(timestamp),
    }

    response = requests.get(f"{BASE_URL}/v2/topics/{topic_id}", headers=headers)
    response.raise_for_status()
    return response.json()
Our API documentation tools cannot automatically generate HMAC signatures. Calculate the signature manually or use a tool like Postman with pre-request scripts.

OAuth scopes

OAuth bots are authorized by scope, not by read / write. Each /v2/* endpoint requires a specific scope — the bot must be granted that scope at creation, and the access token must include it.
ScopeGrants access to
channel:listGET /v2/topics
channel:readGET /v2/topics/{topicId}, GET /v2/topics/external/{externalId}
channel:writePOST /v2/topics, PATCH /v2/topics/{topicId}, POST/DELETE /v2/topics/{topicId}/members
message:readGET /v2/messages/{messageId}, GET /v2/topics/{topicId}/messages
message:sendPOST /v2/messages
message:writePATCH /v2/messages/{messageId}, DELETE /v2/messages/{messageId}, POST /v2/messages/{messageId}/delivered, POST /v2/messages/{messageId}/read
reaction:writePOST /v2/messages/{messageId}/reactions, DELETE /v2/messages/{messageId}/reactions/{reactionId}
task:readGET /v2/tasks, GET /v2/tasks/{taskId}
task:writePOST /v2/tasks, PATCH /v2/tasks/{taskId}, DELETE /v2/tasks/{taskId}
poll:writePOST /v2/polls, POST/DELETE /v2/polls/{pollId}/votes/...
member:readGET /v2/members, GET /v2/members/me
updates:readGET /v2/updates
A token may carry multiple scopes. Request the minimum set you need — narrower scopes limit the blast radius if the token is ever leaked.