# Connecteam Webhooks

Connecteam, the frontline workforce app, sends a JSON webhook when something happens in users, forms, the time clock (time activity), the shift scheduler, tasks or chat. You create webhooks through the Connecteam Webhook API. Webhooks are **not signed**: when you set a `secretKey`, Connecteam sends that exact value in the `x-webhook-secret` header and does not sign or hash the body. Your endpoint authenticates the request by comparing that header with the secret you stored.

> Set a secretKey when you create the webhook through the Connecteam API. Webhooks without one send nothing to verify.

## When to Use This Skill

* How do I receive Connecteam webhooks?
* How do I verify the `x-webhook-secret` header?
* Does Connecteam sign webhook payloads? (No: it sends the shared `secretKey` as-is)
* How do I handle `user_created`, `form_submission`, `clock_in` or `shift_created` events?
* Why does my Connecteam webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyConnecteamRequest(headers, secret = process.env.CONNECTEAM_WEBHOOK_SECRET) {
  if (!secret) return false;                       // never accept when unconfigured
  const received = headers['x-webhook-secret'];    // the secretKey, sent unchanged
  return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}

```

* Header: `x-webhook-secret: <secretKey>` (case-insensitive name, no prefix, not a hash).
* Return `401` when the header is missing or doesn't match, `200` (any 2xx) once handled.
* Python: `hmac.compare_digest` on bytes (encode both sides; passing `str` raises on non-ASCII).
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.

**What this does and doesn't give you.** A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures one request can replay it or send a different body with the same header. Use HTTPS, and rotate the `secretKey` if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/connecteam-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/connecteam-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/connecteam-webhooks/examples/fastapi/).

## HTTP Methods

Only the Chat webhook guide names the method (`POST`, with `User-Agent: connecteam`); the setup page doesn't. The examples accept GET, POST, PUT, PATCH and DELETE on the route, check the credential first for every method, and return 200 for an empty body.

Hookdeck's Connecteam source pre-selects POST without managing the method, so any other method the sender uses must also be allowed on the source.

## Event Types

Dispatch on `eventType`. Most features send `{requestId, company, activityType, eventTimestamp, eventType, data}`; Time Activity is flat (`userId`, `timeClockId`, `timeActivity` at the root, plus `webhookVersion`).

| Feature | Events |
| --- | --- |
| Users | `user_created`, `user_updated`, `user_deleted`, `user_archived`, `user_restored`, `user_promoted`, `user_demoted` |
| Forms | `form_submission`, `form_submission_edited`, `manager_field_updated` |
| Time Activity | `clock_in`, `clock_out`, `auto_clock_out`, `admin_add`, `admin_edit`, `admin_delete`, `user_timesheet_submitted`, `user_timesheet_unsubmitted`, `user_timesheet_approved`, `user_timesheet_reopened`, plus request approval and decline events |
| Shift Scheduler | `shift_created`, `shift_updated`, `shift_deleted`, `availability_status_created`, `availability_status_deleted` |
| Tasks | `task_published`, `task_completed` |
| Chat | `message_created`, `message_updated`, `message_deleted`, `conversation_created`, `conversation_updated`, `conversation_deleted` |

Return any 2xx within 10 seconds, then process asynchronously. A 404 is treated as permanent.

## Environment Variables

```bash
CONNECTEAM_WEBHOOK_SECRET=your-webhook-secret-key   # the secretKey you set on the webhook

```

## Local Development

Connecteam can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 connecteam --path /webhooks/connecteam

```

Use the printed URL as the webhook `url` when you create the webhook through the API.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/connecteam-webhooks/references/overview.md) - What Connecteam webhooks are, events and payload shapes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/connecteam-webhooks/references/setup.md) - Create the webhook through the API with a secretKey
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/connecteam-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/connecteam-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests