Product

Channels and Integrations

The customer writes where they want, the agent replies from a single screen. The original channel is maintained so the response goes back the same way.

Email first

It's the channel that handles most of the volume, so it's the one we care most about: clean parsing, bounce management, DKIM on your outgoing emails.

Chat and WhatsApp

The widget chat creates a ticket as soon as no one is available. WhatsApp Business goes through a number you provide.

API for the rest

Everything the interface does is accessible via REST: tickets, messages, contacts, articles, rules, exports.

acme.open-helpdesk.com/app/settings/channels
Emailsupport@acme.com · MX verified412 this month
Chatweb widget · 14 kB97 this month
WhatsApp+1 555 ** ** **58 this month
APIkey srv_live_… · read/write1,903 calls

POST /webhooks/acme · ticket.created

200 · 84 ms · signature verified

200 · 91 ms · signature verified

502 · retrying in 30 s

Connected channels and webhook delivery log.

Feature details

An API that does everything

Everything the interface does is available under /api/v1: JSON responses, cursor pagination, 600 requests per minute — enough to build without asking permission.

Keys that only open what's needed

Personal tokens or service keys, with read and write scopes per resource: your integration gets only the access it needs.

Webhooks you can rely on

HMAC SHA-256 signatures, five retries with backoff, a 7-day delivery log: your system always knows what was sent to it.

Where your team already talks

Slack and Teams notifications per view or per team, and replies straight from the thread on the Team plan and above.

Your data comes and goes freely

We migrate your Zendesk history with its numbers and its dates. And since the core is under AGPL-3.0, you can install it yourself and access the database directly.

Every call leaves a trace

Every API call shows up in the audit log with the key that made it: who did what is never a mystery.

Verify webhook signature

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(raw: string, header: string, secret: string) {
  const expected = createHmac("sha256", secret).update(raw).digest("hex");
  return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}