# Microsoft Teams notifications

> Post TimeTracking events to Microsoft Teams channels through a signature-verifying relay and a Teams Workflows (Power Automate) webhook.

URL: https://timetracking.tech/developers/integrations/chat/teams
Last updated: 2026-10-24

## 1. Summary

**Status:** Recipe (no Teams app).

**Direction:** TimeTracking to Teams (one-way notifications).

## 2. Mechanism

`TimeTracking webhook → relay (verify signature, enrich, build Adaptive Card) → Teams Workflows webhook → channel post`

Microsoft is retiring Office 365 connectors (classic "Incoming Webhook"). Use the Teams **Workflows** app template *"Post to a channel when a webhook request is received"*, which gives an HTTPS trigger URL that accepts an Adaptive Card payload. Power Automate cannot verify HMAC signatures without code, so keep the verification in the relay (Azure Function, Cloudflare Worker, or an iPaaS code step).

Assumption — verify with your Microsoft 365 admin: Workflows availability and the exact accepted payload depend on tenant policy; the template expects `{"type":"message","attachments":[{"contentType":"application/vnd.microsoft.card.adaptive","content":{…}}]}`.

## 3. Prerequisites

- Teams channel and permission to add a workflow; the workflow trigger URL.
- An admin user (dashboard *Regels & koppelingen*) or an API key with scope `admin` to create the webhook; a read key for enrichment.

## 4. Entity mapping

Same event-to-message mapping as [Slack](https://timetracking.tech/developers/integrations/chat/slack#4-entity-mapping). Each event becomes an Adaptive Card with a title (event type), facts (person, client, date, minutes as h:mm) and a link to the dashboard (`https://app.timetracking.tech/`). Stateless; nothing stored.

## 5. Step-by-step setup

1. In Teams: channel → *Workflows* → *Post to a channel when a webhook request is received*; copy the URL.
2. As TimeTracking admin, create a webhook to your relay with the chosen events; save the secret.
3. Relay core (reuse `verify()` and `text()` from the [Slack relay](https://timetracking.tech/developers/integrations/chat/slack#5-step-by-step-setup)) and post:

```js
async function postToTeams(title, facts) {
  const card = {
    type: "message",
    attachments: [{
      contentType: "application/vnd.microsoft.card.adaptive",
      content: {
        $schema: "http://adaptivecards.io/schemas/adaptive-card.json", type: "AdaptiveCard", version: "1.4",
        body: [
          { type: "TextBlock", text: title, weight: "Bolder", wrap: true },
          { type: "FactSet", facts: facts.map(([title, value]) => ({ title, value: String(value) })) },
        ],
        actions: [{ type: "Action.OpenUrl", title: "Open TimeTracking", url: "https://app.timetracking.tech/" }],
      },
    }],
  };
  const r = await fetch(process.env.TEAMS_WORKFLOW_URL, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(card) });
  if (!r.ok) throw new Error(`Teams ${r.status}`);
}

// example for conflict.created after enrichment:
// await postToTeams("Conflict between hour sources", [["Person", "Sanne Janssen"], ["Client", "Bakkerij Ruijters"],
//   ["Date", "2026-12-01"], ["Candidates", "manual_office 8:00 · mail 7:30"]]);
```

## 6. Sync schedule and triggering events

Event-driven; up to ~5 minutes delivery delay. Suggested: `conflict.created`, `export.completed`, `period.locked` to an office channel; `leave.requested` to a planning channel.

## 7. Corrections, conflicts and locked periods

Informational only. Resolve conflicts in TimeTracking. No event fires on resolution.

## 8. Testing checklist

- [ ] A test event appears as a card in the channel.
- [ ] Wrong signature returns `401`.
- [ ] Relay responds within 10 s (Workflows calls can be slow: post asynchronously if needed).

## 9. Troubleshooting

| Problem | Fix |
|---|---|
| Workflow returns `400` | Payload must be the `message` + Adaptive Card attachment shape. |
| Workflow returns `202` but nothing posted | Check the flow run history in Power Automate; the flow owner needs channel access. |
| No TimeTracking delivery | Public HTTPS only, 2xx within 10 s, no redirects. |

## 10. Limits and known gaps

Same as [Slack](https://timetracking.tech/developers/integrations/chat/slack#10-limits-and-known-gaps): 21 event types, ID-only payloads, no interactive approval.

Use `POST /webhooks/{id}/test` (admin) to send a `webhook.test` event, and `POST /webhook-deliveries/{id}/replay` to resend a failed one.

## 11. Build it with Claude

```text
Build an Azure Function (Node 20, TypeScript) that receives TimeTracking webhooks, verifies tt-signature
(t=<unix>,v1=hex HMAC-SHA256(secret,"<t>.<raw body>"), 5 min tolerance), enriches names via
GET https://app.timetracking.tech/api/v1/people and /placements (Bearer key, { data }, snake_case), and posts an
Adaptive Card to a Teams Workflows webhook URL ({type:"message", attachments:[{contentType:
"application/vnd.microsoft.card.adaptive", content:{…}}]}). Support all 21 TimeTracking event types, route
them to different channels by config, escape user text, and include tests.
```
