Agent skill
Connecteam Webhooks Skill
Receive and authenticate Connecteam webhooks. Use when setting up a Connecteam webhook endpoint, checking the x-webhook-secret header, debugging 401s, or handling workforce events like user_created, form_submission, clock_in, clock_out, shift_created and task_completed.
Install this skill
npx skills add hookdeck/webhook-skills --skill 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-secretheader? - Does Connecteam sign webhook payloads? (No: it sends the shared
secretKeyas-is) - How do I handle
user_created,form_submission,clock_inorshift_createdevents? - Why does my Connecteam webhook endpoint return 401?
Verification (core)
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
401when the header is missing or doesn't match,200(any 2xx) once handled. - Python:
hmac.compare_digeston bytes (encode both sides; passingstrraises 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/, examples/nextjs/, 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_, user_timesheet_approved, user_timesheet_reopened, plus request approval and decline events |
| Shift Scheduler | shift_created, shift_updated, shift_deleted, availability_, availability_ |
| 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
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):
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 - What Connecteam webhooks are, events and payload shapes
- references/setup.md - Create the webhook through the API with a secretKey
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests