Skip to content

For developers

Connect your platform to VeyraDesk

A standing contract any VeyraDesk client system can implement independently — so your users submit and track support tickets without a second login. This is not specific to any one client; any number of systems can connect this way.

Phase 1 · liveProtocol veyradesk-sso-ticket.v1Inbound email reply · not yet live

1. Overview

01

User clicks "Support" in your app

Already signed in to your system — nothing new to log into.

02

Your backend: POST /your/mint-ticket

Behind your own auth. Looks up the real identity server-side and signs a 90-second HMAC ticket.

03

Browser redirects to VeyraDesk

GET /api/v1/support/consume-ticket/<integrationKey>?...&sig=...

04

VeyraDesk verifies, provisions, redirects

Checks the signature and expiry, auto-provisions (or reuses) a Customer, sets an httpOnly session cookie.

05

User lands on /portal/tickets

Already authenticated, scoped only to their own account.

Nothing is shared between client systems except the convention itself — VeyraDesk never needs your database, your auth, or your session; you never need VeyraDesk’s.

2. One-time setup: create your Integration

Done by a VeyraDesk tenant owner or admin — through the VeyraDesk dashboard (Settings → Client integrations → New integration) or the API directly:

Request
POST /api/v1/integrations
Authorization: Bearer <veyradesk tenant admin JWT>
Content-Type: application/json

{
  "name": "ClinicaERP",
  "key": "clinicaerp",
  "type": "clinicaerp",
  "notifyEmailAddress": "support-ops@yourcompany.com"
}
FieldRulesNotes
name1–120 charsDisplay name, shown in the VeyraDesk dashboard only.
keylowercase, single hyphens, 2–63 chars, globally uniqueBecomes part of your consume-ticket URL — see §4.
typefree text, 1–60 charsNever a fixed enum — "izemnext", "clinicaerp", or your own.
notifyEmailAddressoptional, valid emailWhere VeyraDesk sends a "new ticket" notification — one team inbox, not per end-user.
inboundWebhookSecret is returned exactly once, in this response, and never again. Store it immediately. If it’s lost, you must create a brand new Integration — there is no rotation endpoint on the existing one — and any users already auto-provisioned under the old Integration will be provisioned again as separate Customers the next time they visit through the new one.

You now have three values for your own environment:

  • key → your VEYRADESK_SSO_INTEGRATION_KEY
  • inboundWebhookSecret → your VEYRADESK_SSO_SECRET
  • VeyraDesk’s base origin, e.g. https://support.veyradesk.com → your VEYRADESK_SUPPORT_BASE_URL

3. Sign tickets in your own backend

Code you write, in your own stack, behind your own authentication. The only requirement: a currently logged-in user of your system is the one triggering it. VeyraDesk never sees your login flow.

3.1 · The claims

interface SupportSsoTicketClaims {
  externalAccountId: string;   // your Company / Clinic / Branch id
  externalAccountName: string; // its display name
  externalUserId: string;      // the end user's id in YOUR system
  email: string;               // the end user's real email
  displayName: string;         // the end user's real display name
}

Always populate these from a real server-side lookup at mint time. A JWT or session whose claims carry only ids is fine to arrive with — the name, email, and display name sent to VeyraDesk must always be freshly read from your own database, never trusted directly from client input.

3.2 · The signing algorithm

NAMESPACE = "veyradesk-sso-ticket.v1"   // fixed — do not change
TTL_MS    = 90_000                      // 90 seconds — keep this exact

exp     = now_ms + TTL_MS
message = [NAMESPACE, externalAccountId, externalAccountName,
           externalUserId, email, displayName, exp].join(".")
signature = HMAC_SHA256(secret, message), hex-encoded

secret is your VEYRADESK_SSO_SECRET — the exact plaintext inboundWebhookSecret you were shown once (§2). Never expose it to browser or client-side code; signing happens server-side only.

The 90-second TTL is intentionally short: this ticket exists to survive one immediate browser redirect, not to be stored, reused, or handed to a background job. Build and consume it in the same request cycle.

3.3 · Minimal reference implementation (Node.js)

import { createHmac } from "node:crypto";

const NAMESPACE = "veyradesk-sso-ticket.v1";
const TICKET_TTL_MS = 90 * 1000;

interface SupportSsoTicketClaims {
  externalAccountId: string;
  externalAccountName: string;
  externalUserId: string;
  email: string;
  displayName: string;
}

function sign(secret: string, claims: SupportSsoTicketClaims, exp: number): string {
  const message = [
    NAMESPACE,
    claims.externalAccountId,
    claims.externalAccountName,
    claims.externalUserId,
    claims.email,
    claims.displayName,
    exp,
  ].join(".");
  return createHmac("sha256", secret).update(message).digest("hex");
}

export function buildSupportTicketUrl(claims: SupportSsoTicketClaims, nowMs = Date.now()): string {
  const secret = process.env.VEYRADESK_SSO_SECRET!;
  const base = process.env.VEYRADESK_SUPPORT_BASE_URL!.replace(/\/$/, "");
  const integrationKey = process.env.VEYRADESK_SSO_INTEGRATION_KEY!;
  const exp = nowMs + TICKET_TTL_MS;
  const sig = sign(secret, claims, exp);

  const url = new URL(`${base}/api/v1/support/consume-ticket/${integrationKey}`);
  url.searchParams.set("externalAccountId", claims.externalAccountId);
  url.searchParams.set("externalAccountName", claims.externalAccountName);
  url.searchParams.set("externalUserId", claims.externalUserId);
  url.searchParams.set("email", claims.email);
  url.searchParams.set("displayName", claims.displayName);
  url.searchParams.set("exp", String(exp));
  url.searchParams.set("sig", sig);
  return url.toString();
}

Port this to whatever language your backend is written in — HMAC-SHA256 and URL query-string building are available everywhere. There is no VeyraDesk SDK to install.

Fail loudly, with a clear error, if any of the three env vars above is unset — never construct or return a broken or partial URL. A dedicated "not configured" error type that your own controller translates into a 503 works well here.

3.4 · Expose it as your own endpoint

Wrap the above in an endpoint of your own, guarded only by your own normal authentication — e.g. POST /support/mint-ticket, returning:

{ "ticketUrl": "https://support.veyradesk.com/api/v1/support/consume-ticket/clinicaerp?..." }

4. Add an entry point in your UI

A "Support" button or nav item in your own frontend, calling your mint-ticket endpoint and opening the returned URL:

async function handleSupport() {
  const res = await fetch("/api/support/mint-ticket", { method: "POST" });
  if (!res.ok) { showError("Support is temporarily unavailable."); return; }
  const { ticketUrl } = await res.json();
  window.open(ticketUrl, "_blank");
}

5. What VeyraDesk does (nothing for you to implement)

GET /api/v1/support/consume-ticket/:integrationKey has no authentication guard of its own — the signature on the query string is the entire gate. It:

  1. Looks up the Integration by key; rejects if missing, soft-deleted, or disabled.
  2. Recomputes the HMAC over the claims it received and compares it, constant-time, against sig; rejects on any mismatch or if exp has passed.
  3. On the first visit from a given externalAccountId under this Integration, auto-provisions a VeyraDesk Customer for it. On any later visit, reuses the same one — your users never need a separate VeyraDesk login or account, and are never shown a customer-creation step.
  4. Mints a VeyraDesk customer-portal session (httpOnly cookie, 60-minute TTL) and redirects the browser to VeyraDesk’s own /portal/tickets.

From there, your user submits tickets and sees their own ticket list and detail pages — strictly scoped to their own externalAccountId. They can never see another account’s tickets, even under the same Integration. There’s no portal login page and no portal refresh token: once the session expires, the user clicks "Support" again from your app for a fresh one.

Any failure — bad signature, expired ticket, unknown or disabled Integration key — returns a generic 403 with a plain-text body, never revealing whether an Integration key exists at all.

6. Email notifications

Optional, and configured entirely on VeyraDesk’s side. Each VeyraDesk tenant sets up its own Amazon SES sender under Settings → Support email — there is no platform-wide shared sender. Once configured:

  • A new ticket from your Integration notifies that Integration’s notifyEmailAddress (§2).
  • A staff reply notifies the ticket’s submitter at the email address carried in your signed ticket.

Misconfigured or unconfigured email on the VeyraDesk tenant’s side never blocks ticket creation or replies — the notification itself silently fails and is logged on VeyraDesk’s side. There is nothing for your client system to configure here.

7. Reference: customer-portal ticket API

For context only — your end users interact with these through VeyraDesk’s own portal UI after the SSO handoff. You don’t call these yourself.

Method & pathPurpose
GET /api/v1/portal/me{ email, displayName } of the signed-in portal session.
GET /api/v1/portal/ticketsList tickets for the signed-in account.
POST /api/v1/portal/tickets{ subject, description?, priority?, category? }
GET /api/v1/portal/tickets/:idTicket detail — 404 if it belongs to another account.
GET /api/v1/portal/tickets/:id/messagesThread, excluding internal staff notes.
POST /api/v1/portal/tickets/:id/messages{ body } — a reply.

All of the above require the customer_portal_session cookie set in §5; none accept or need a bearer token.

8. Not yet live: inbound-email reply threading

The intended design is documented and unit-tested as pure logic only — not wired to a live endpoint. A client replying by email to a notification would have that reply threaded back onto the right ticket via a per-ticket reply address (ticket-<id>@<tenant’s inbound domain>). This needs real AWS infrastructure on VeyraDesk’s side: an SES receipt rule, a verified inbound MX record, an S3 bucket, an SNS topic — none of which exists yet. Until it does, your users reply through the VeyraDesk portal UI directly, not by replying to the notification email.

9. Security checklist

  • VEYRADESK_SSO_SECRET is only ever read server-side — it never reaches browser/client code, logs, or error messages.
  • Ticket claims are always re-derived from your own server-side database at mint time, never taken directly from client input or trusted blindly from a JWT.
  • Your mint-ticket endpoint is behind your own normal authentication — there is no separate "is this a real client" check on VeyraDesk’s side beyond the signature itself.
  • Full ticket URLs (containing sig) are treated as one-time, short-lived bearer credentials — avoid logging them in shared or long-retention logs.
  • Your endpoint fails with a clear, explicit error — never a broken URL — if any required env var is missing.

Ready to connect?

Create your Integration in VeyraDesk

Already a VeyraDesk customer? Create an Integration from Settings to get your key and secret. New here? Start on the Free plan — no credit card required.

  • Fewer on-site visits
  • Authorised, time-limited sessions
  • Every customer and site in one place
  • Built for support teams