Skip to content

API Keys & Webhooks

TeamsDesk exposes a public REST API at /api/v1/* and pushes events to your endpoints over webhooks. This page is the reference for both: how to create credentials, what scopes do, what events you can subscribe to, and how to verify signed deliveries.

Both surfaces are managed in the Teams app under Global Admin → API Keys and Global Admin → Webhooks. You must be a Global Admin to create or revoke either.


API keys

Creating a key

  1. Open TeamsDesk and switch to the Global Admin workspace.
  2. Go to API Keys and click Create key.
  3. Give the key a descriptive name (the integration that will use it), pick the scopes it needs, and optionally set an expiry.
  4. Copy the key immediately. It is shown exactly once. TeamsDesk only stores a hash; if you lose it, revoke and create a new one.

The raw key looks like:

tdsk_abc123def456ghi789jkl012mno345pqr678stu901vwx

The first 12 characters (tdsk_xxxxxxx) are stored as the public prefix so you can identify the key later in the dashboard.

Scopes

| Scope | What it grants | | -------- | ------------------------------------------------------------------------ | | read | Read tickets, comments, knowledge base, users, departments | | write | Create and update tickets and comments | | admin | Full access (everything read and write cover, plus admin endpoints) |

You must select at least one scope. admin is a superset, so granting it alone is enough; you don't also need read/write.

Authenticating

Send the key as a Bearer token. The same Authorization header carries either an API key or a Teams SSO token; TeamsDesk picks the right path based on the prefix.

curl https://your-tenant.teamsdesk.example.com/api/v1/tickets \
  -H "Authorization: Bearer tdsk_abc123def456ghi789jkl012mno345pqr678stu901vwx"

Replace the host with your tenant's TeamsDesk API base URL (your Global Admin sees this in the API Keys workspace).

Rate limit

Each key is allowed 600 requests per minute on a rolling window (about 10 req/s sustained). Exceeding it returns 429 Too Many Requests. Back off and retry.

Revocation and expiry

  • Revoking is instant. The next request with that key gets 401 Unauthorized. Revocation cannot be undone — issue a new key.
  • Expiring keys (if you set an expiry at creation) get the same 401 once their expiry passes. Set short-lived expiries for keys handed to contractors or one-off scripts.

TeamsDesk records lastUsedAt, lastUsedIp, and a useCount for every key so you can spot stale or compromised credentials.

Best practices

  • One key per integration. If you rotate the key for one system, you don't break the others.
  • Pick the smallest scope that works. Don't grant admin if read is enough.
  • Set an expiry on anything not run by your own internal team.
  • Treat keys like passwords: never commit them to source control, never paste them into a shared chat. Store them in a secret manager (Azure Key Vault, AWS Secrets Manager, 1Password, etc.).

Webhooks

TeamsDesk delivers events to any HTTPS endpoint you control. Use webhooks to react to ticket activity in real time — kick off automations, mirror tickets into another system, or post into a chat channel.

Creating a subscription

  1. Open Global Admin → Webhooks and click Create webhook.
  2. Give it a name and the endpoint URL (must be https:// on a public host).
  3. Pick the events you want, or * to receive all of them.
  4. Copy the signing secret shown on the success screen — it is shown exactly once, just like an API key. The prefix is whsec_.

You can pause a subscription with the Active toggle without losing its history, or delete it (history is retained for audit).

Events

| Event | Fires when | | ----------------- | ----------------------------------------------------------- | | ticket.created | A new ticket is opened | | ticket.updated | A ticket field changes (status, severity, assignment, etc.) | | ticket.closed | A ticket is closed with a resolution | | ticket.assigned | A ticket is assigned or auto-assigned to an agent | | approval.requested | A service request approval is sent to approvers | | approval.completed | A service request approval is approved or rejected | | sla.breached | A response or resolution SLA breach is detected | | csat.submitted | A requester submits satisfaction feedback | | device.detection.created | An endpoint agent reports a device detection | | comment.added | A public comment is posted (internal notes are excluded) |

Use * in the event list to subscribe to all events, including any added in the future.

Delivery format

Every delivery is a POST with Content-Type: application/json and the following headers:

User-Agent: TeamsDesk-Webhooks/1.0
X-TeamsDesk-Event: ticket.created
X-TeamsDesk-Event-Id: 7d2f8c4e-...        (use this as your idempotency key)
X-TeamsDesk-Delivery: 9b1a3f12-...        (per-attempt id, for support)
X-TeamsDesk-Signature: t=1714248000,v1=ab12cd34...

The body is JSON with a stable shape:

{
  "event": "ticket.created",
  "createdAt": "2026-04-27T15:00:00.000Z",
  "tenantId": "76025cc5-4a4f-4606-891c-afa4ff7a3013",
  "data": {
    "id": 51948,
    "title": "Print spooler stuck — please restart",
    "status": "In Progress"
  }
}

data is the event-specific payload. Test deliveries (sent from the Send sample button) include "test": true and a synthetic data block for the event type you choose — they're useful for confirming your handler reaches the open internet without waiting for a real ticket.

Verifying the signature

The X-TeamsDesk-Signature header has two comma-separated fields:

  • t= — Unix timestamp of when TeamsDesk signed the payload
  • v1= — hex-encoded HMAC-SHA256

To verify, compute the HMAC of ${t}.${rawJsonBody} using your signing secret, then timing-safe compare to v1. Reject anything where t is older than ~5 minutes — that prevents replay attacks.

Node.js example:

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const t = Number(parts.t);
  const v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5-minute window

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(v1, "hex"),
  );
}

A few must-dos:

  • Verify against the raw request body, not the parsed JSON. Re-serializing changes whitespace and breaks the signature.
  • Use a constant-time comparison (crypto.timingSafeEqual in Node, hmac.compare_digest in Python). Never ==.
  • Store the secret in a secret manager. Don't ship it in client code.

Delivery, retries, and dead-letter

  • TeamsDesk waits up to 8 seconds for your endpoint to respond. Anything slower is treated as a failure.
  • Any HTTP 2xx response counts as a success. Anything else is a failure.
  • Failed deliveries retry up to 6 times total with exponential backoff: 60s → 5m → 30m → 2h → 12h → 24h.
  • After 6 attempts, the delivery is marked dead. It will not retry automatically — you can replay it from the Deliveries panel once your endpoint is fixed.

Always respond fast — return a 200 as soon as you've durably enqueued the work, and process it asynchronously. If your handler does heavy lifting inline, you'll keep hitting the 8s timeout.

Inspecting deliveries

The Deliveries panel shows the last 50 attempts per subscription with status, attempt count, response code, and a truncated response body so you can see what your endpoint sent back. You can:

  • Replay any delivery — schedules an immediate fresh attempt.
  • Send sample — choose any subscribed event type and fire a synthetic delivery inline so you see the result instantly.

Allowed endpoints

For your safety, TeamsDesk refuses to deliver to internal addresses: localhost, 127.0.0.1, 0.0.0.0, *.local, RFC 1918 ranges (10.*, 192.168.*, 172.16-31.*), and cloud metadata endpoints. Use a public-facing HTTPS URL — front it with a tunnel (ngrok, Cloudflare Tunnel) during local development.


Common patterns

  • Mirror tickets into another system: subscribe to ticket.created + ticket.updated, use X-TeamsDesk-Event-Id as the idempotency key, upsert.
  • Post to a chat channel: subscribe to ticket.created filtered to a specific severity or department, format data into your channel's webhook format.
  • Escalate SLA risk: subscribe to sla.breached, filter on data.breachType or data.departmentId, then post to Teams or page the owner.
  • Report approvals and CSAT: subscribe to approval.requested, approval.completed, and csat.submitted, then write rows to SharePoint, Dataverse, or a warehouse.
  • Trigger a build / runbook: subscribe to a custom keyword in ticket.updated, hand off to your CI or runbook engine over the public API using a key with read scope.
  • Backfill history with the API: webhooks only fire forward in time. To seed an existing system, page through GET /api/v1/tickets once with an admin-scoped key, then keep state in sync with webhooks.

Next: read Power Automate & Zapier for connector import steps and recipes, Integrations for the Microsoft-side surface, or AI and Automation for AI features.

Start your free trial →