ZenZap Long Polling API

Long polling allows your integration to fetch outbound events instead of receiving webhooks. Use `GET /v2/updates` with: - `offset`: value returned as `nextOffset` from the previous response - `limit`: max updates per request (default 50, max 100) - `timeout`: wait time in seconds when no updates are available (default 0, max 30) Event payloads in polling responses match webhook `data` payloads for the same event type. This includes `data.message.attachments[]` with signed URLs for file/image/video/audio messages.

OpenAPI Specification

zenzap-long-polling-api-openapi.yml Raw ↑
openapi: 3.0.3
info:
  title: Zenzap External Integration Agentic Long Polling API
  description: "API for external applications to integrate with Zenzap.\n## Getting Started\nWelcome to the Zenzap External Integration API documentation. This API is used to integrate with Zenzap.\nAs a context we would like to familiarize you with the Zenzap platform and how it works.\nZenzap is a platform for creating and managing topics and messages.\nTopics (Zenzap term for group chats/channels/conversations) are used to create and manage conversations with your team. Topics are used to group messages and tasks together.\nMessages are used to send and receive messages with your team.\nTasks are used to create and manage tasks with your team.\nMembers are used to manage your team members.\nAPI keys are used to authenticate your requests to the Zenzap API.\nAPI keys are created and managed by the Zenzap admin user.\n\nWhen you create a new API key, it would create a new bot user in Zenzap, on which behalf you can send messages, create topics, create tasks and manage members.\nAll actions you perform with the API key will be performed on behalf of the bot user.\nThe bot can be used within the scope of your organization. You can create topics, send messages, create tasks and manage members on behalf of the bot.\n1. On a Zenzap admin user, go to https://app.zenzap.co/console\n2. Create a new API key with the needed permissions\n3. Copy the API key and secret from the API key settings\n4. For each request:\n   - Add the Authorization header with your API key\n   - Generate a Unix timestamp in milliseconds\n   - Calculate the HMAC-SHA256 signature:\n     - For POST/PUT/PATCH/DELETE: Sign `{timestamp}.{body}`\n     - For GET: Sign `{timestamp}.{uri}` (e.g., `/v2/members?limit=10`)\n   - Add the X-Signature header with the hex-encoded signature\n   - Add the X-Timestamp header with the timestamp used in the payload\n\n## Authentication\nAll API endpoints require two forms of authentication:\n\n1. **Bearer Token**: Include your API key in the Authorization header:\n```\nAuthorization: Bearer YOUR_API_KEY\n```\n\n2. **Request Signing**: Include an HMAC signature and timestamp in headers:\n```\nX-Signature: <HMAC-SHA256 hex-encoded signature>\nX-Timestamp: <Unix timestamp in milliseconds>\n```\nThe signature includes a timestamp for replay protection. Requests older than 5 minutes are rejected.\n\nThe signature payload differs by HTTP method:\n- **POST/PUT/PATCH/DELETE requests**: Sign `{timestamp}.{body}`\n- **GET requests**: Sign `{timestamp}.{uri}`\n\nThe signature is calculated using HMAC-SHA256 with your API secret.\n\n  **How to calculate:**\n  1. Get the current Unix timestamp in milliseconds\n  2. Determine the payload:\n      - **POST/PUT/PATCH/DELETE**: Use `{timestamp}.{body}` (e.g., `1699564800000.{\"topicId\":\"123\"}`)\n      - **GET**: Use `{timestamp}.{uri}` (e.g., `1699564800000./v2/members?limit=10`)\n  3. Calculate HMAC-SHA256 of the payload using your API secret\n  4. Hex-encode the result (64 character lowercase string)\n  5. Include timestamp in X-Timestamp header\n\n  **Example (Python):**\n  ```python\n  import json\n  import hmac\n  import hashlib\n  import time\n  import requests\n\n  def create_topic(name: str, members: list[str], description: str = None, external_id: str = None) -> dict:\n      \"\"\"Create a topic in Zenzap with HMAC signature and replay protection.\"\"\"\n\n      # Build request body\n      body = {\n          \"name\": name,\n          \"members\": members,\n      }\n      if description:\n          body[\"description\"] = description\n      if external_id:\n          body[\"externalId\"] = external_id\n\n      # Serialize body (no spaces for consistent signing)\n      body_json = json.dumps(body, separators=(\",\", \":\"))\n\n      # Get timestamp for replay protection\n      timestamp = int(time.time() * 1000)  # Unix milliseconds\n\n      # Create HMAC-SHA256 signature with timestamp\n      signature_payload = f\"{timestamp}.{body_json}\"\n      signature = hmac.new(\n          API_SECRET.encode(),\n          signature_payload.encode(),\n          hashlib.sha256\n      ).hexdigest()\n\n      # Make request\n      headers = {\n          \"Authorization\": f\"Bearer {API_KEY}\",\n          \"Content-Type\": \"application/json\",\n          \"X-Signature\": signature,\n          \"X-Timestamp\": str(timestamp),\n      }\n\n      response = requests.post(f\"{BASE_URL}/v2/topics\", headers=headers, data=body_json)\n      response.raise_for_status()\n      return response.json()\n  ```\n\n  **Note:** Our API documentation tools cannot automatically generate HMAC signatures. You will need to calculate this manually or use a tool like Postman with pre-request scripts.\n\n\n## Rate Limits\nAPI requests are rate limited per organization. Contact support for specific limits.\n"
  version: 2.0.0
  contact:
    name: Zenzap Support
    url: https://zenzap.co/support
servers:
- url: https://api.zenzap.co
  description: Production server
security:
- bearerAuth: []
  hmacSignature: []
- oauth2ClientCredentials: []
tags:
- name: Long Polling
  description: 'Long polling allows your integration to fetch outbound events instead of receiving webhooks.


    Use `GET /v2/updates` with:

    - `offset`: value returned as `nextOffset` from the previous response

    - `limit`: max updates per request (default 50, max 100)

    - `timeout`: wait time in seconds when no updates are available (default 0, max 30)


    Event payloads in polling responses match webhook `data` payloads for the same event type.

    This includes `data.message.attachments[]` with signed URLs for file/image/video/audio messages.

    '
paths:
  /v2/updates:
    get:
      summary: Get updates (long polling)
      description: 'Retrieve outbound events for bots configured with **polling** delivery mode.


        Long polling behavior:

        - If updates are available after `offset`, they are returned immediately

        - If none are available and `timeout > 0`, the request waits up to `timeout` seconds

        - If still no updates, returns an empty list with `nextOffset`

        - Returns `409` if delivery mode is not polling, or if `offset` is no longer available


        Message attachment payloads are included inline in polling events, the same as webhooks:

        `data.message.attachments[]` with signed download URLs (including audio transcription metadata when available).


        Use `nextOffset` from each response as `offset` in the next request.

        '
      operationId: getUpdates
      security:
      - bearerAuth: []
        hmacSignature: []
      - oauth2ClientCredentials:
        - updates:read
      tags:
      - Long Polling
      parameters:
      - $ref: '#/components/parameters/XSignature'
      - $ref: '#/components/parameters/XTimestamp'
      - name: offset
        in: query
        required: false
        description: Opaque offset returned by a previous `nextOffset`. Omit on the first request.
        schema:
          type: string
      - name: limit
        in: query
        required: false
        description: 'Maximum updates to return (default: 50, max: 100)'
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
      - name: timeout
        in: query
        required: false
        description: 'Long polling timeout in seconds (default: 0, max: 30)'
        schema:
          type: integer
          minimum: 0
          maximum: 30
          default: 0
      responses:
        '200':
          description: Updates retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdatesResponse'
              example:
                updates:
                - updateId: AAAAAAABAAAAAAAAAAABAA==
                  eventType: message.created
                  createdAt: 1699564800000
                  data:
                    message:
                      id: 660e8400-e29b-41d4-a716-446655440001
                      topicId: 550e8400-e29b-41d4-a716-446655440000
                      senderId: 550e8400-e29b-41d4-a716-446655440001
                      senderType: user
                      senderName: Alice Johnson
                      type: text
                      text: Hello team!
                      createdAt: 1699564800000
                    truncated: false
                nextOffset: AAAAAAABAAAAAAAAAAABAA==
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    WebhookMessageDeletedData:
      type: object
      required:
      - messageId
      - topicId
      properties:
        messageId:
          type: string
          format: uuid
          description: The deleted message ID
        topicId:
          type: string
          format: uuid
          description: The topic where the message was deleted
        deletedAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when deletion occurred
          example: 1699564800000
        deletedBy:
          type: string
          description: The user ID who deleted the message
        message:
          type: object
          description: Minimal message snapshot
          properties:
            id:
              type: string
              format: uuid
            topicId:
              type: string
              format: uuid
            senderId:
              type: string
    WebhookTranscription:
      type: object
      description: 'Transcription data for audio attachments (voice messages).


        **Transcription flow:**

        1. `message.created` arrives with `transcription.status: "Pending"`

        2. `message.updated` arrives when transcription completes with `status: "Done"` and `text` populated

        '
      properties:
        status:
          type: string
          enum:
          - Pending
          - Started
          - Done
          - Failed
          description: 'Transcription status:

            - `Pending`: Queued for transcription

            - `Started`: Transcription in progress

            - `Done`: Transcription complete, `text` field populated

            - `Failed`: Transcription failed

            '
        text:
          type: string
          description: The transcribed text (only present when status is "Done")
          example: Hey team, just a quick update on the project...
    WebhookAttachment:
      type: object
      description: 'File attachment with download URL. URLs are signed and expire after 60 minutes.

        Download files promptly after receiving the webhook.

        '
      properties:
        id:
          type: string
          description: The attachment ID
        type:
          type: string
          enum:
          - image
          - file
          - video
          - audio
          description: The attachment type
        name:
          type: string
          description: The original file name
          example: document.pdf
        url:
          type: string
          format: uri
          description: 'Signed URL to download the attachment.

            **Important:** URLs expire after 60 minutes.

            '
          example: https://storage.zenzap.co/attachments/...?token=...&expires=...
        transcription:
          $ref: '#/components/schemas/WebhookTranscription'
      example:
        id: att_550e8400-e29b-41d4-a716-446655440088
        type: audio
        name: voice-note.mp3
        url: https://storage.zenzap.co/attachments/...
        transcription:
          status: Done
          text: Hey team, just a quick update on the project...
    WebhookReactionObject:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Reaction ID
        messageId:
          type: string
          format: uuid
          description: The reacted message ID
        topicId:
          type: string
          format: uuid
          description: Topic ID containing the message
        userId:
          type: string
          description: The reacting user ID
        emoji:
          type: string
          description: The reaction emoji
    PollingUpdate:
      type: object
      required:
      - updateId
      - eventType
      - createdAt
      - data
      properties:
        updateId:
          type: string
          description: Opaque update identifier. Use as offset in subsequent polling calls.
          example: AAAAAAABAAAAAAAAAAABAA==
        eventType:
          type: string
          enum:
          - message.created
          - message.updated
          - message.deleted
          - reaction.added
          - reaction.removed
          - member.added
          - member.removed
          - topic.updated
          description: The update event type
          example: message.created
        createdAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when the update was created
          example: 1699564800000
        data:
          description: Event payload. Structure matches the webhook `data` payload for the same event type.
          oneOf:
          - $ref: '#/components/schemas/WebhookMessageCreatedData'
          - $ref: '#/components/schemas/WebhookMessageUpdatedData'
          - $ref: '#/components/schemas/WebhookMessageDeletedData'
          - $ref: '#/components/schemas/WebhookReactionData'
          - $ref: '#/components/schemas/WebhookMemberData'
          - $ref: '#/components/schemas/WebhookTopicUpdatedData'
    MessageMention:
      type: object
      properties:
        id:
          type: string
          description: The mentioned profile ID
          example: 550e8400-e29b-41d4-a716-446655440001
        name:
          type: string
          description: Display name used for the mention text
          example: Alice Johnson
    WebhookLocation:
      type: object
      description: Shared location data
      properties:
        latitude:
          type: string
          description: Latitude coordinate
          example: '37.7749'
        longitude:
          type: string
          description: Longitude coordinate
          example: '-122.4194'
        name:
          type: string
          description: Location name (optional)
          example: San Francisco Office
        address:
          type: string
          description: Street address (optional)
          example: 123 Market St, San Francisco, CA
    WebhookMessageCreatedData:
      type: object
      required:
      - message
      properties:
        message:
          $ref: '#/components/schemas/WebhookMessage'
        truncated:
          type: boolean
          description: Whether the payload content was truncated
          example: false
    WebhookTask:
      type: object
      description: 'Task snapshot sent when a task is created, updated, or its status changes.

        The `action` field indicates what triggered this snapshot.

        '
      properties:
        id:
          type: string
          description: The task ID
        action:
          type: string
          enum:
          - Added
          - Updated
          - Deleted
          - MarkedAsDone
          - MarkedAsOpened
          - Replied
          description: 'What triggered this task snapshot:

            - `Added`: Task was created

            - `Updated`: Task details were changed

            - `Deleted`: Task was removed

            - `MarkedAsDone`: Task was completed

            - `MarkedAsOpened`: Task was reopened

            - `Replied`: Comment was added to task

            '
        title:
          type: string
          description: Task title
          example: Review documentation
        text:
          type: string
          description: Task description
          example: Please review the API docs by Friday
        status:
          type: string
          enum:
          - Open
          - Done
          description: Current task status
        assignee:
          type: string
          description: User ID of the assignee
          example: 550e8400-e29b-41d4-a716-446655440001
        dueDate:
          type: integer
          format: int64
          description: Due date as Unix timestamp in milliseconds
          example: 1699651200000
        isDueDateTimeSelected:
          type: boolean
          description: Whether a specific time was selected for the due date
        parentId:
          type: string
          description: Parent task ID (for subtasks)
        subItemsCount:
          type: integer
          description: Number of subtasks
          example: 3
    WebhookReactionData:
      type: object
      required:
      - messageId
      - topicId
      - emoji
      - userId
      properties:
        messageId:
          type: string
          format: uuid
          description: The message that was reacted to
        topicId:
          type: string
          format: uuid
          description: The topic containing the message
        emoji:
          type: string
          description: The emoji that was added/removed
          example: 👍
        userId:
          type: string
          description: The user who added/removed the reaction
          example: 550e8400-e29b-41d4-a716-446655440001
        reactionId:
          type: string
          format: uuid
          description: Reaction ID (present when available)
        createdAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when the reaction event was created
          example: 1699564800000
        reaction:
          $ref: '#/components/schemas/WebhookReactionObject'
    UpdatesResponse:
      type: object
      required:
      - updates
      - nextOffset
      properties:
        updates:
          type: array
          items:
            $ref: '#/components/schemas/PollingUpdate'
          description: Ordered list of updates after the provided offset
        nextOffset:
          type: string
          description: Opaque offset to pass as `offset` in the next `/v2/updates` call
          example: AAAAAAABAAAAAAAAAAABAA==
    WebhookContact:
      type: object
      description: Shared contact information
      properties:
        name:
          type: string
          description: Contact name
          example: John Doe
        phoneNumbers:
          type: array
          items:
            type: string
          description: Phone numbers
          example:
          - '+1234567890'
        emails:
          type: array
          items:
            type: string
          description: Email addresses
          example:
          - john@example.com
        role:
          type: string
          description: Job title or role
          example: Engineer
        location:
          type: string
          description: Location or office
          example: San Francisco
        linkedIn:
          type: string
          description: LinkedIn profile URL
          example: https://linkedin.com/in/johndoe
        profileId:
          type: string
          description: Zenzap profile ID if the contact is a Zenzap user
          example: 550e8400-e29b-41d4-a716-446655440001
    WebhookTopicUpdatedData:
      type: object
      required:
      - topicId
      properties:
        topicId:
          type: string
          format: uuid
          description: The topic that was updated
        name:
          type: string
          description: New topic name (only present if changed)
        description:
          type: string
          description: New topic description (only present if changed)
        actorId:
          type: string
          description: The actor who made the update
        changes:
          type: object
          description: Map of changed fields and their new values
          additionalProperties: true
    WebhookMessage:
      type: object
      description: 'Message object as delivered in webhook events. Includes additional fields

        not present in the REST API response, such as message type-specific data.

        '
      required:
      - id
      - topicId
      - senderId
      - senderName
      - senderType
      - type
      - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: The message ID
        topicId:
          type: string
          format: uuid
          description: The topic ID where the message was sent
        senderId:
          type: string
          description: The sender's member ID
          example: 550e8400-e29b-41d4-a716-446655440001
        senderName:
          type: string
          description: The sender's display name
          example: Alice Johnson
        senderType:
          type: string
          enum:
          - user
          - bot
          - system
          description: The type of sender
        type:
          type: string
          enum:
          - text
          - image
          - file
          - video
          - audio
          - location
          - task
          - contact
          description: 'The message type:

            - `text`: Regular text message

            - `image`, `file`, `video`, `audio`: Message with attachment

            - `location`: Shared location

            - `task`: Task snapshot (created, updated, completed, etc.)

            - `contact`: Shared contact

            '
        text:
          type: string
          description: 'The message text content (may be empty for non-text types).

            Mention tokens are preserved as `<@profileId>`.

            '
        previousText:
          type: string
          description: Previous message text (present on `message.updated` when text changed)
        createdAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when the message was created
        updatedAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when the message was last updated
        parentId:
          type: string
          format: uuid
          description: ID of the message being replied to (for threaded replies)
        mentionedProfiles:
          type: array
          items:
            type: string
          description: IDs of mentioned users in the message
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/WebhookAttachment'
          description: Array of file attachments
        mentions:
          type: array
          items:
            $ref: '#/components/schemas/MessageMention'
          description: Array of user mentions in the message
        location:
          $ref: '#/components/schemas/WebhookLocation'
        task:
          $ref: '#/components/schemas/WebhookTask'
        contact:
          $ref: '#/components/schemas/WebhookContact'
    WebhookMessageUpdatedData:
      type: object
      required:
      - message
      properties:
        message:
          $ref: '#/components/schemas/WebhookMessage'
        updatedFields:
          type: array
          items:
            type: string
          description: List of fields that changed
          example:
          - text
        truncated:
          type: boolean
          description: Whether the payload content was truncated
          example: false
    WebhookMemberData:
      type: object
      required:
      - topicId
      - memberIds
      properties:
        topicId:
          type: string
          format: uuid
          description: The topic where the member was added/removed
        memberIds:
          type: array
          items:
            type: string
          description: One or more member IDs affected by the operation
          example:
          - 550e8400-e29b-41d4-a716-446655440003
        memberId:
          type: string
          description: Single member ID (present when exactly one member changed)
          example: 550e8400-e29b-41d4-a716-446655440003
        memberType:
          type: string
          enum:
          - user
          - bot
          description: Type of member for single-member events
          example: user
        actorId:
          type: string
          description: The actor who triggered the member change
          example: 550e8400-e29b-41d4-a716-446655440001
        createdAt:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds when the event payload was created
          example: 1699564800000
  parameters:
    XSignature:
      name: X-Signature
      in: header
      required: false
      description: "HMAC signature of the request for authentication and replay protection.\n\n**Required only when authenticating with a static API key.** If you are using an OAuth access token (issued by `POST /oauth/token`), omit this header — the JWT carries all the authentication and integrity guarantees.\n\n**Replay Protection:** The signature includes a timestamp to prevent replay attacks.\nRequests with timestamps older than 5 minutes are rejected.\n\nThe signature payload differs by HTTP method:\n- **POST/PUT/PATCH/DELETE**: HMAC-SHA256 of `{timestamp}.{body}`\n- **GET**: HMAC-SHA256 of `{timestamp}.{uri}`\n\nThe signature is calculated as:\n1. Get the current Unix timestamp in milliseconds\n2. Determine the payload:\n   - For POST/PUT/PATCH/DELETE: Use `{timestamp}.{body}` where body is the request body\n   - For GET: Use `{timestamp}.{uri}` where uri is the full request URI (e.g., `/v2/members?limit=10`)\n3. Calculate HMAC-SHA256 of the combined payload using your API secret\n4. Hex-encode the output\n5. Include the timestamp in the `X-Timestamp` header\n\nExample for GET request to `/v2/members?limit=10`:\n```\ntimestamp = 1699564800000\npayload = \"1699564800000./v2/members?limit=10\"\nsignature = HMAC-SHA256(secret, payload)\nX-Signature: hex(signature)\nX-Timestamp: 1699564800000\n```\n\nExample for POST request with body `{\"topicId\":\"123\",\"text\":\"Hello\"}`:\n```\ntimestamp = 1699564800000\npayload = '1699564800000.{\"topicId\":\"123\",\"text\":\"Hello\"}'\nsignature = HMAC-SHA256(secret, payload)\nX-Signature: hex(signature)\nX-Timestamp: 1699564800000\n```\n\nFor `multipart/form-data` requests, sign the exact raw request body bytes\n(including boundaries and file bytes) as transmitted.\n"
      schema:
        type: string
        pattern: ^[a-f0-9]{64}$
        example: a3d5f8e7c2b1d4f6a8e9c7b5d3f1a2e4b6c8d0f2e4a6b8c0d2e4f6a8b0c2d4e6
    XTimestamp:
      name: X-Timestamp
      in: header
      required: false
      description: 'Unix timestamp in milliseconds when the request was created.

        Used for replay protection — requests older than 5 minutes are rejected.


        **Required only when authenticating with a static API key.** Omit when using an OAuth access token.

        '
      schema:
        type: integer
        format: int64
        example: 1699564800000
  responses:
    InternalServerError:
      description: Internal server error
      content:
        text/plain:
          schema:
            type: string
          example: internal server error
    Unauthorized:
      description: Unauthorized - invalid or missing API token
      content:
        text/plain:
          schema:
            type: string
          example: unauthorized
    Conflict:
      description: Conflict - request cannot be completed in current state
      content:
        text/plain:
          schema:
            type: string
          example: delivery mode is not polling
    BadRequest:
      description: Bad request - invalid input
      content:
        text/plain:
          schema:
            type: string
          example: text is required
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'Bearer token for the request. Two flavors:


        - **Static API key** — pass your API key (the value returned as `apiKey` when the bot was created). Must be paired with `X-Signature` + `X-Timestamp` (the `hmacSignature` scheme).

        - **OAuth access token** — pass the JWT returned by `POST /oauth/token`. No signature headers are required.

        '
    hmacSignature:
      type: apiKey
      in: header
      name: X-Signature
      description: 'HMAC-SHA256 signature for request verification. Required **only** when authenticating with a static API key. Omit when using an OAuth access token.

        '
    oauth2ClientCredentials:
      type: oauth2
      description: 'OAuth 2.0 `client_credentials` grant for API-key bots. Use the `clientId` and `clientSecret` returned when the bot was created (or rotated) to mint short-lived access tokens. See [Authentication](/api-reference/authentication) for details.


        Access tokens are bearer JWTs and expire after 1 hour. There is no refresh token — re-mint with the client credentials when the token expires.

        '
      flows:
        clientCredentials:
          tokenUrl: https://api.zenzap.co/oauth/token
          scopes:
            channel:list: List topics the bot belongs to
            channel:read: Read topic metadata
            channel:write: Create/update topics and manage members
            message:read: Read messages
            message:send: Send messages
            message:write: Edit / delete / mark-delivered / mark-read messages
            reaction:write: Add and remove reactions on messages
            task:read: Read tasks
            task:write: Create / update / delete tasks
            poll:write: Create polls and cast / retract votes
            member:read: List organization members
            updates:read: Long-poll for outbound events