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
- Open TeamsDesk and switch to the Global Admin workspace.
- Go to API Keys and click Create key.
- Give the key a descriptive name (the integration that will use it), pick the scopes it needs, and optionally set an expiry.
- 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
401once 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
adminifreadis 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
- Open Global Admin → Webhooks and click Create webhook.
- Give it a name and the endpoint URL (must be
https://on a public host). - Pick the events you want, or
*to receive all of them. - 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 payloadv1=— 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.timingSafeEqualin Node,hmac.compare_digestin 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
2xxresponse 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, useX-TeamsDesk-Event-Idas the idempotency key, upsert. - Post to a chat channel: subscribe to
ticket.createdfiltered to a specific severity or department, formatdatainto your channel's webhook format. - Escalate SLA risk: subscribe to
sla.breached, filter ondata.breachTypeordata.departmentId, then post to Teams or page the owner. - Report approvals and CSAT: subscribe to
approval.requested,approval.completed, andcsat.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 withreadscope. - Backfill history with the API: webhooks only fire forward in time. To seed an existing system, page through
GET /api/v1/ticketsonce with anadmin-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.