Custom channels · Webhooks

Custom channels

Connect any messaging platform to Pager: client messages land in the shared inbox, and managers' replies go back to your platform.

Getting started

Overview

A custom channel connects a platform Pager has no ready-made integration for: your own messenger, a website chat, a mobile app or a third-party service. Managers work with these conversations just like with Instagram or Telegram.

Your server
Inbound events

A client wrote on your platform — you send message.created to Pager's webhook.

Outbound messages

A manager replied in Pager — Pager sends a POST to your server's URL.

Pager
  • Everything is JSON over HTTPS, and both directions are signed with one channel key.
  • You identify clients and messages with your own IDs — externalId. Pager creates the client and conversation on the first message.
  • New messages count towards your organization's message allowance, same as other channels.

Getting started

Connecting a channel

An organization admin connects the channel in the Pager dashboard.

  1. 1

    Open Settings → Channels and click Підключити власний канал (Connect your own channel).

  2. 2

    Enter a channel name — managers see it in the inbox — and a URL for sending messages: your server's address where Pager will send managers' replies.

  3. 3

    Once connected, the channel card shows an API Key like chan_sk_live_… — that's the channel key. Add it to your server's settings.

A channel key works for one channel only and is unrelated to the Pager API key (pgr_live_…). Each custom channel has its own key.
A channel key lets you post messages on behalf of your clients. Keep it on your server and never ship it in browser or mobile app code.

Getting started

Authentication

Both directions are signed with the channel key in the x-channel-key header.

  • Your server → Pager. Send the key in x-channel-key with every webhook request. Pager uses it to identify the channel and organization.
  • Pager → your server. Pager sends the same key in x-channel-key. Compare it with yours and reject requests with any other key — otherwise anyone who knows your URL could message your clients as a manager.

Inbound: your server → Pager

Pager webhook

Send all inbound events as a POST to a single URL. The event field sets the event type: message.created, message.edited or message.deleted.

POSThttps://api.pager.co.ua/api/webhooks/custom

Headers

  • x-channel-keyheader

    Channel key.

  • Content-Typeheader

    application/json.

  • One event per request. There's no batching.
  • Successful processing always returns 200 with an action field saying what was done — see Responses and errors.

New messagemessage.created

Adds a message to the conversation with a client. If the client writes for the first time, Pager creates the client and conversation.

Request body

  • eventstringrequired

    message.created.

  • client.externalIdstringrequired

    The client's ID on your platform, up to 255 characters. Pager uses it to find the client in this channel.

  • client.namestring | nulloptional

    Client name, up to 100 characters. Omitted — stays as is; null — cleared.

  • client.imageUrlstring | nulloptional

    Avatar URL. Omitted — stays as is.

  • message.externalIdstringrequired

    The message's ID on your platform, up to 255 characters.

  • message.directionenumrequired

    incoming — a message from the client. outgoing — a message your operator sent outside Pager, so the history in Pager stays complete.

  • message.textstring | nulloptional

    Text, up to 8000 characters.

  • message.attachmentsobject[]optional

    Up to 20 attachments — see Attachments.

  • Sending the same message.externalId again doesn't create a duplicate: Pager updates the existing message's text and attachments. So it's safe to retry a request you got no response to.
  • An incoming message marks the conversation unread. An outgoing one is stored as read and doesn't change the conversation's state.
  • Leading and trailing whitespace is trimmed, and \r\n line breaks become \n.
  • A new message counts towards your organization's message allowance; a repeat of the same externalId doesn't.
curl -X POST "https://api.pager.co.ua/api/webhooks/custom" \
  -H "x-channel-key: $PAGER_CHANNEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"message.created","client":{"externalId":"user_58213","name":"Олена Коваль","imageUrl":"https://cdn.example.com/avatars/58213.jpg"},"message":{"externalId":"msg_90412","direction":"incoming","text":"Добрий день! Чи є доставка у Львів?","attachments":[]}}'
Response · 200
{
  "ok": true,
  "action": "created_or_deduped"
}

Editmessage.edited

Changes the text or attachments of a message Pager already has, and marks it as edited.

Request body

  • eventstringrequired

    message.edited.

  • message.externalIdstringrequired

    ID of the message to change.

  • message.textstring | nulloptional

    New text, up to 8000 characters. Omitted, null or an empty string — the text stays as is.

  • message.attachmentsobject[]optional

    A new list of attachments that fully replaces the old one. Omitted — attachments stay as is; [] — remove all.

  • Pager looks for a message with this externalId in your channel — incoming or outgoing. Managers' messages only have an externalId if your server returned one in its response to the outbound message.
  • If the message isn't found, Pager returns 200 with action: "edit_ignored_not_found".
curl -X POST "https://api.pager.co.ua/api/webhooks/custom" \
  -H "x-channel-key: $PAGER_CHANNEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"message.edited","message":{"externalId":"msg_90412","text":"Добрий день! Чи є доставка у Львів і скільки вона коштує?"}}'
Response · 200
{
  "ok": true,
  "action": "edited"
}

Deletemessage.deleted

Permanently deletes a message from Pager — managers no longer see it.

Request body

  • eventstringrequired

    message.deleted.

  • message.externalIdstringrequired

    ID of the message to delete.

  • Like edits, it works for incoming and outgoing messages with a known externalId.
  • If the message isn't found, Pager returns 200 with action: "delete_ignored_not_found".
curl -X POST "https://api.pager.co.ua/api/webhooks/custom" \
  -H "x-channel-key: $PAGER_CHANNEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"message.deleted","message":{"externalId":"msg_90412"}}'
Response · 200
{
  "ok": true,
  "action": "deleted"
}

Inbound: your server → Pager

Responses and errors

Successful processing always returns 200 with ok: true. The action field says what was done:

actionMeaning
created_or_dedupedThe message was created, or already existed and was updated (a repeated message.created).
editedThe message was changed.
edit_ignored_not_foundNo message with this externalId — nothing changed.
deletedThe message was deleted.
delete_ignored_not_foundNo message with this externalId — nothing deleted.

Errors come with an error field:

HTTPerrorMeaning
400Invalid payloadThe body doesn't match the schema. details.fieldErrors shows which fields failed.
401Missing x-channel-keyThe x-channel-key header is missing.
401Invalid channel keyNo such key — the channel may have been deleted.
403Channel is not CustomThe key belongs to a channel of another type.
403Inbound disabledInbound messages are turned off for the channel.
500Server errorInternal Pager error.
Response · 400
{
  "error": "Invalid payload",
  "details": {
    "formErrors": [],
    "fieldErrors": {
      "client": [
        "Invalid input: expected string, received undefined"
      ]
    }
  }
}
On 500 and network errors, retry after a pause — thanks to externalId, retries never create duplicates. Retrying won't fix a 4xx: check the key and the request body.

Outbound: Pager → your server

Request from Pager

When a manager sends a message in a conversation of this channel, Pager immediately makes a POST to the URL set when the channel was connected. Messages sent via the Pager API arrive the same way. The body has the same format as the inbound message.created event.

Headers

  • x-channel-keystring

    Channel key — verify it, see Authentication.

  • Content-Typestring

    application/json.

Request body

  • eventstring

    Always message.created.

  • client.externalIdstring

    The client's ID on your platform — the one you sent in inbound events.

  • message.pagerMessageIdstring

    The message's ID in Pager. Use it to drop repeated deliveries.

  • message.directionenum

    Always outgoing.

  • message.textstring | null

    Text, or null if the manager only sent files.

  • message.attachmentsobject[]

    Attachments — see Attachments.

POSTRequest from Pager
{
  "event": "message.created",
  "client": {
    "externalId": "user_58213"
  },
  "message": {
    "pagerMessageId": "c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f",
    "direction": "outgoing",
    "text": "Так, відправляємо Новою поштою, доставка 1–2 дні. Ось тарифи:",
    "attachments": [
      {
        "type": "image",
        "payload": {
          "url": "https://files.pager.co.ua/…/tariffs.png?X-Amz-Expires=86400&…"
        }
      }
    ]
  }
}

Outbound: Pager → your server

Your server's response

Respond with a 2xx as soon as you've accepted the message, and return the message's ID on your platform as JSON.

Your response · 200
{
  "externalMessageId": "msg_90415"
}
  • Pager stores externalMessageId (or externalId) as the message's ID. Without it you won't be able to edit or delete this message later.
  • The message counts towards your organization's message allowance only if you responded with a 2xx and returned externalMessageId.
  • The manager only sees the message as sent after your response, so respond quickly and do any heavy processing afterwards.

Delivery and retries

  • If your server responds with a non-2xx or doesn't respond at all, Pager retries twice more — after 0.2 and 0.6 seconds.
  • If all three attempts fail, the message is saved in Pager marked as undelivered, and the manager sees that. Pager doesn't resend it automatically later.
Because of retries, the same request may arrive more than once — for example, if you processed the message but your response got lost. Drop duplicates by message.pagerMessageId.

Outbound: Pager → your server

Handler example

A minimal handler: verifies the key, drops repeats, passes the message to your platform and returns its ID.

import express from "express"

const app = express()
app.use(express.json())

// pagerMessageId -> externalMessageId. In production, use a database or Redis
const processed = new Map()

app.post("/pager/outbound", async (req, res) => {
  // 1. The request really comes from Pager
  if (req.get("x-channel-key") !== process.env.PAGER_CHANNEL_KEY) {
    return res.status(401).end()
  }

  const { client, message } = req.body

  // 2. A repeat of an already processed message
  if (processed.has(message.pagerMessageId)) {
    return res.json({ externalMessageId: processed.get(message.pagerMessageId) })
  }

  // 3. Send to the client on your platform
  const sent = await platform.sendMessage({
    to: client.externalId,
    text: message.text,
    attachments: message.attachments,
  })

  processed.set(message.pagerMessageId, sent.id)
  res.json({ externalMessageId: sent.id })
})
platform.sendMessage is your own sending code. The rest can be used as is.

Reference

Attachments

Attachments are an array of { type, payload: { url } } objects — the same in inbound events and in requests from Pager.

Fields

  • typeenumrequired

    image, video, audio, document or file.

  • payload.urlstringrequired

    A public http(s) link to the file, up to 2000 characters.

attachments
[
  {
    "type": "image",
    "payload": {
      "url": "https://cdn.example.com/uploads/photo.jpg"
    }
  },
  {
    "type": "document",
    "payload": {
      "url": "https://cdn.example.com/uploads/invoice.pdf"
    }
  }
]
  • Pager stores the link, not a copy of the file, so it must stay available for as long as managers need the conversation history.
  • Links to localhost and internal networks (127.*, 10.*, 192.168.*) are silently dropped; the rest of the message is saved.
  • Up to 20 attachments per message.
  • In requests from Pager, links are temporary and expire after 24 hours — download files right away.
Didn't find an answer?Contact us
Custom channels — Pager documentation