Перейти к содержимому

Portal API reference

The 6 operations behind a support screen in your own application, generated from the API itself. For what any of it is for, start with the guide.

Reading a path

Every path below is relative to https://api.helpwing.app/portal and takes the same one or two ids, so they are not repeated under each operation: {public_key} is your project's public key, pk_…, and {ticket_id} the conversation the path names.

The first operation is called from your server and needs the project's identity secret. Every other one is called from the browser, with the token that returned in X-Helpwing-Session, and reaches only the conversations of the customer it was minted for.

Signing a user in — from your server

Exchange a signed claim for a browser session

POST/{public_key}/sessions/

Called from your server, never from the browser. It is the one portal endpoint that takes a signature, and the signature needs the project's identity secret - the same one the chat widget's userHash is keyed with, found in the dashboard under the widget's Identity settings.

user_hash = hex(hmac_sha256(key=identity_secret, message=external_id))

Hand the returned token to the page you are rendering and send it back in X-Helpwing-Session on every other call here. It reaches exactly one customer's conversations, and it expires - when it does, ask your server for another, because your server is the only party that knows whether that person is still signed in.

There is no unverified mode. The widget can afford one, because a visitor who proves nothing still gets a conversation of their own; here an unproved claim would be a request to read a stranger's support history, so an unsigned or wrongly signed claim is refused outright.

The customer is matched on external_id. A first-time caller whose email matches a customer who has never been identified takes over that record - which is the point: the conversations they had by email before they ever signed in are the history the portal exists to show them. A record already carrying a different external_id is left alone.

Body

external_idstringrequired
The id your application knows this person by. The customer is matched on it, and it is the value the signature covers.
user_hashstringrequired
hex HMAC-SHA256 of `external_id`, keyed with the project identity secret.
emailstring (email)
Their address. Used the first time only, to take over the record they already had from before they ever signed in - the conversations the portal exists to show them.
namestring
Display name. Fills a blank on the record; it never overwrites what an agent corrected.
metadataobject
Free-form JSON merged into the customer's record: plan, account id, seat count. Signed by your server, so unlike a conversation's own metadata it is a fact rather than a claim.

Responses

  • 201Session
  • 403 — the error envelope · The signature does not match the user id.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Conversations — from the browser

List the customer's conversations

GET/{public_key}/conversations/

Every conversation belonging to the customer this session was minted for, newest activity first - email, chat and portal alike. A support history is one list, and which door a question came in by is the customer's least interesting fact about it.

Paginated like every other list in the API. Spam is left out: a conversation an agent has marked as spam is one nobody is going to answer, and listing it promises a reply that is not coming.

Query

pageinteger
Which page to return. Defaults to 1.
page_sizeinteger
Conversations per page, up to 200. Defaults to 25.

Responses

  • 200a page of PortalConversation
  • 401 — the error envelope · The session token is unknown or has expired. Mint a new one.
  • 403 — the error envelope · This origin is not on the project's allow list.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Open a conversation from the portal

POST/{public_key}/conversations/

Files a new ticket for the customer this session belongs to, on the project's default inbox, with the project's default assignee and priority - the same routing an inbound email gets. The ticket.created and message.created webhooks fire.

The conversation's source is portal, which is what tells the agent answering it that the customer is reading their replies in the project's own application and not in a mailbox.

metadata is free-form JSON kept on the ticket and shown beside it - what the form asked, the page they were on, the order they were looking at. It comes from the browser, so treat it as a claim: anything that has to be true belongs in the session exchange's metadata instead, which your server signs and which lands on the customer's record.

Body — JSON, or multipart to attach files

subjectstringrequired
What the conversation is called, as the customer typed it. Required.
body_textstring
Their first message. Required unless the conversation opens with a file.
filesstring (binary)[]
Files to attach, as multipart. The widget's per-message caps apply here too.
metadataobject
Free-form JSON stored on the ticket and shown to the agent beside it - the form's own fields, the page the customer was on, an order number. At most 4096 bytes, and sent by the browser, so it is a claim rather than a fact.

Responses

  • 201PortalTranscript
  • 401 — the error envelope · The session token is unknown or has expired. Mint a new one.
  • 403 — the error envelope · This origin is not on the project's allow list.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Read a conversation

GET/{public_key}/conversations/{ticket_id}/

The transcript the customer is allowed to see - their own messages, agent replies and system entries, never internal notes - plus a cursor to follow it with.

Responses

  • 200PortalTranscript
  • 401 — the error envelope · The session token is unknown or has expired. Mint a new one.
  • 403 — the error envelope · This origin is not on the project's allow list.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Reply in a conversation

POST/{public_key}/conversations/{ticket_id}/messages/

Appends a message, exactly as an inbound email on the same ticket would: the inbox ordering, preview and counters follow it, a resolved conversation reopens when the project enables reopen_on_customer_reply, the timeline records customer_replied, the message.created webhook fires and the assignee is notified.

Unlike the chat widget, reading a conversation here does not suppress the emailed copy of the agent's reply. A chat panel is open or it is not; a portal is a page somebody may have left open in a tab since Tuesday, and a customer who never comes back to it should still hear the answer.

Send a client_message_id if you retry. Repeating one returns the message it already stored instead of storing a second.

Body — JSON, or multipart to attach files

body_textstring
What they wrote. Required unless the message carries a file.
filesstring (binary)[]
Files to attach, as multipart. The widget's per-message caps apply here too.
client_message_idstring
An id the sender makes up for this message, e.g. a UUID. Sending it again returns the message it already stored instead of storing a second one.

Responses

  • 201PortalTranscript
  • 401 — the error envelope · The session token is unknown or has expired. Mint a new one.
  • 403 — the error envelope · This origin is not on the project's allow list.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Poll a conversation for replies

GET/{public_key}/conversations/{ticket_id}/updates/

The customer's half of the live update feed, over the same cursor the chat widget and the agent inbox use. Send the cursor from the previous response as since; with wait the request is held open until an agent replies or the wait expires.

Long polling rather than server-sent events, for the reason the widget documents: EventSource cannot set a request header, so a stream would have to carry the session token in the query string.

Query

sincestring
Cursor from the previous response.
waitinteger
Seconds to hold the request open waiting for a reply. 0 (default) returns at once.

Responses

  • 200PortalUpdates
  • 401 — the error envelope · The session token is unknown or has expired. Mint a new one.
  • 403 — the error envelope · This origin is not on the project's allow list.
  • 404 — the error envelope · No project has that public key, or the conversation is not this customer's.

Objects

What comes back, described once each rather than under every endpoint that returns it. Every field here is read-only: these are responses, and what a request accepts is listed with the operation itself.

PortalConversation

One conversation as a row in the customer's own list.

idstring (uuid)read-only
Use it in the paths below. Only this customer's conversations resolve.
referencestringread-only
The conversation's public identifier. Opaque, and the only one a customer ever sees.
subjectstringread-only
What the conversation is called.
statusstringread-only
`open`, `pending`, `resolved` or `closed`. * `open` - Open * `pending` - Pending * `resolved` - Resolved * `closed` - ClosedOne ofopen, pending, resolved, closed
sourcestringread-only
Which door it came in by: `email`, `chat`, `portal` or `api`. * `email` - Email * `chat` - Chat * `api` - API * `portal` - PortalOne ofemail, chat, api, portal
last_message_atstring (date-time)read-only
When anybody last wrote in it. What the list is ordered by.
last_message_previewstringread-only
The opening of the most recent message, for the list row.
message_countintegerread-only
How many messages it holds, internal notes excluded.
created_atstring (date-time)read-only
When it was opened.

PortalCustomer

The person the session belongs to, as they are allowed to see themselves.

idstring (uuid)read-only
Helpwing's own id for this customer.
external_idstringread-only
The id you signed in the claim, echoed back.
emailstring (email)read-only
The address their conversations are had at, when there is one.
namestringread-only
Their display name, as an agent sees it.
avatar_urlstring (uri)read-only
Their avatar, when the project has one for them.

PortalTranscript

One conversation and everything in it the customer may read.

conversationPortalConversationread-only
The conversation itself.
messagesPublicMessage[]read-only
Oldest first. Only what the customer may read - their own messages, agent replies and system entries. Internal notes are never here. Just the new message when one was posted.
typingobject| nullread-only
`{name}` of the agent typing a reply, or null.
cursorstringread-only
Pass back as `?since=` to the updates endpoint.

PortalUpdates

What happened on the conversation since the cursor you sent.

cursorstringread-only
Send this back as the next `?since=`.
has_changesbooleanread-only
Whether anything happened. False is what a `wait` that timed out returns.
statusstringread-only
The conversation's status right now.
messagesPublicMessage[]read-only
Messages written after the cursor you sent, oldest first.
typingobject| nullread-only
`{name}` of the agent typing a reply, or null.

PublicMessage

One message as the visitor sees it: who wrote it, what it says, what is attached.

idstring (uuid)read-only
The message's own id, stable across every read of the conversation.
authorstringread-only
`agent`, `customer` or `system`.
author_namestringread-only
Who wrote it, as a name. An agent's display name, never their email address.
author_avatar_urlstringread-only
Their avatar, when there is one.
body_textstringread-only
What was written, as markdown. Render it the way the dashboard does.
body_htmlstringread-only
The same message as HTML, when the channel it arrived by carried one.
attachmentsobject[]read-only
Files on the message: `id`, `filename`, `content_type`, `size_bytes`, `is_inline`, `content_id` and a `url` that expires. An `is_inline` file is a picture written into `body_text` as `![name](cid:<content_id>)`; show it there and leave it out of the file list.
created_atstring (date-time)read-only
When it was written. The transcript is ordered by it.
client_message_idstringread-only
The id the sender made up for this message, echoed back. Only ever set on a message this customer sent, and only when they sent one - which is what lets a client that drew the message before sending it recognise its own copy.

Session

A minted session. The token is returned here and never again.

tokenstringread-only
Send it back in `X-Helpwing-Session` on every portal call. Store it in memory, not on disk.
expires_atstring (date-time)read-only
When the token stops working.
expires_inintegerread-only
Seconds until the token stops working.
customerPortalCustomerread-only
Who the session was minted for.
NextREST API →Open and answer tickets from your own backend with a project API key, and get told when one changes.