Guide to Connecteam Webhooks: Features and Best Practices
Connecteam webhooks tell your systems what is happening in a deskless workforce: an employee clocks in or out, a timesheet is approved, a form is submitted, a shift is created or a chat message is posted. If your payroll, HR or operations tools need that activity as it happens, Connecteam's webhooks deliver it without polling the API.
This guide covers how Connecteam webhooks are created and scoped, the events and payload shapes for each feature, how to authenticate requests that Connecteam doesn't sign, and the practices that keep a receiver reliable.
What are Connecteam webhooks?
A Connecteam webhook belongs to one feature: users, forms, time activity, the shift scheduler, tasks or chat. You create it through the Webhook API with a URL, the feature, the list of event types you want, and optionally an entityId that narrows it to one object, such as a single Time Clock. Without a scope, it receives matching events from the whole company.
When an event fires, Connecteam sends a JSON request to the URL. Requests are not signed. If you set a secretKey on the webhook, Connecteam "sends that exact value in the x-webhook-secret header. It does not sign or hash the request body." Your receiver compares the header with the value it stored.
Connecteam webhook features
| Feature | Details |
|---|---|
| Configuration | Webhook API: POST /settings/v1/webhooks, with settings.write |
| Scope | One featureType per webhook, optionally narrowed by entityId |
| Event selection | eventTypes array. A PUT replaces the whole list |
| Authentication | Optional secretKey, sent unchanged in x-webhook-secret |
| Signature | None |
| Method | POST, as documented in the Chat guide |
| Body | JSON. Envelope shape varies by feature |
| Versions | webhookVersion, default 1. Shift webhooks also have 2 |
| Response window | 10 seconds. Any 2xx is success |
| Retries | Up to retryLimit (default 3) after the first attempt, with no fixed delay. 404 is never retried |
| Source IPs | Not published |
Common events
Connecteam documents 52 event types across its six features:
| 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, request approval and decline events, admin_day_locked, user_timesheet_submitted, user_timesheet_approved and more |
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 |
Most features share one envelope. Here is Connecteam's user_archived example:
{
"requestId": "f04c4bff-0db4-41db-b9e5-03f3de8f5092",
"company": "your_company_id",
"activityType": "User",
"eventTimestamp": 1731596054,
"eventType": "user_archived",
"data": [
{
"id": 9063791
}
]
}
The shapes differ in ways that matter to a parser:
user_createdanduser_updatedcarry the full user underdata, with auserIdfield. The other user events carry only{ "id": ... }.- Time Activity events are flat.
userId,timeClockIdandtimeActivitysit at the root withwebhookVersion, not underdata, and empty optional fields are left out rather than sent asnull. - Older time activity and chat events also send
evnetTimestamp, a deprecated misspelling ofeventTimestamp. ReadeventTimestamp. - Shift webhooks with
webhookVersion1 send one call per assigned user. Version 2 sends one call per shift with allassignedUserIds, and Connecteam recommends it for use with the V2 API.
All timestamps are Unix seconds.
Setting up Connecteam webhooks
You need an API key (or OAuth) with the settings.write scope, and the account must include Webhooks. Generate a long random secret first, for example with openssl rand -hex 32, and store it server-side.
curl --request POST \
--url https://api.connecteam.com/settings/v1/webhooks \
--header 'Content-Type: application/json' \
--header "X-API-KEY: $CONNECTEAM_API_KEY" \
--data '{
"name": "User sync",
"url": "https://example.com/webhooks/connecteam",
"featureType": "users",
"eventTypes": ["user_created", "user_updated", "user_deleted"],
"secretKey": "your-long-random-secret"
}'
For a Time Clock, set featureType to time_activity and entityId to the Time Clock ID as a string, such as "67". Timesheet period events such as admin_day_locked use the same time_activity webhook.
To add events later, GET the webhook first, then PUT the existing eventTypes plus the new ones. Connecteam's docs warn: "Events you omit are no longer delivered."
With Hookdeck. Connecteam is a supported source in Hookdeck. Create a source with the Connecteam type, paste the secretKey into Secret Key, and use the Hookdeck source URL as the webhook's url. Hookdeck's setup note says to set a secretKey when you create the webhook through the Connecteam API, because webhooks without one send nothing to verify. The source pre-selects POST without managing the method. Connecteam's Chat guide documents POST and no other method appears in its docs, so if a delivery ever shows up with another method in Hookdeck's request log, allow that method on the source. A request without the right header still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.
Securing Connecteam webhooks
Connecteam's own advice is to compare the received header with your stored secret using a constant-time comparison. Check lengths before calling crypto.timingSafeEqual, which throws on buffers of different lengths.
const crypto = require('crypto');
const express = require('express');
function verifyConnecteamRequest(headers) {
const secret = process.env.CONNECTEAM_WEBHOOK_SECRET; // the webhook's secretKey
if (!secret) return false; // fail closed when unconfigured
const received = headers['x-webhook-secret'];
if (typeof received !== 'string') return false;
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(secret, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/connecteam', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyConnecteamRequest(req.headers)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Route on event.eventType. Time Activity fields are at the root, others under data.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_connecteam_request(headers) -> bool:
secret = os.environ.get("CONNECTEAM_WEBHOOK_SECRET")
if not secret:
return False
received = headers.get("x-webhook-secret") # case-insensitive in Starlette and Flask
if not received:
return False
return hmac.compare_digest(received.encode("utf-8"), secret.encode("utf-8"))
A static header proves the sender knows the secret, but it doesn't prove the body is unchanged, and it gives no replay protection: nothing is signed and Connecteam sends no signed timestamp. Use an HTTPS URL, which the API requires, use a long random secretKey, keep it out of logs, and update the webhook with a new one if it might have leaked.
Make Connecteam webhooks production-ready. Hookdeck Event Gateway checks
x-webhook-secreton every request, stores each clock, shift and form event, and keeps retrying delivery to your service long after Connecteam's three retries are spent.
Connecteam webhook limitations and pain points
Only three retries
The Problem: A short outage on your side can lose time clock or form events for good.
Why It Happens: Connecteam retries a failed delivery up to the webhook's retryLimit, which defaults to 3 after the first attempt, and retries "may arrive without a fixed delay". A 404 is treated as permanent and not retried at all.
Workarounds:
- Return a
2xxwithin 10 seconds and do the work afterwards. - Never return
404from a live route, even for events you don't handle. - Reconcile from Connecteam's API after an outage, for example time activities for the affected period.
How Hookdeck Can Help: Hookdeck acknowledges each request as soon as it arrives, stores it, and delivers it to your service with automatic retries on a schedule you control. Issues alert you when deliveries fail, and any event can be replayed.
requestId isn't an event ID
The Problem: Deduplicating on requestId drops real events.
Why It Happens: Connecteam says requestId "is a correlation ID, not a globally unique event ID. Different events caused by one action can share it." Retries keep the same value.
Workarounds:
- Build the idempotency key from
requestId,eventTypeand the affected entity, as Connecteam recommends: the user ID, the time activity ID, or a message or conversation ID.
How Hookdeck Can Help: Hookdeck deduplication can use a combination of fields as the key, so retried deliveries are dropped without collapsing different events from one action.
Several payload shapes
The Problem: A parser written for one feature fails on another.
Why It Happens: Most features nest data under data, Time Activity is flat, shift payloads change between versions 1 and 2, and some events still send evnetTimestamp.
Workarounds:
- Branch on
activityTypeor the webhook's feature before reading fields. - Treat missing keys as absent, since Time Activity omits empty optional fields.
- Pin
webhookVersionexplicitly when you create a scheduler webhook.
How Hookdeck Can Help: Hookdeck transformations can reshape each feature's payload into one format before it reaches your service.
The secret is optional
The Problem: A webhook created without a secretKey sends requests with nothing to check.
Why It Happens: secretKey is an optional field on the create request.
Workarounds:
- Always set
secretKey, and reject requests without the header. - If a webhook was created without one, add it with a
PUT.
How Hookdeck Can Help: Hookdeck's Connecteam source requires the Secret Key, so any request without the matching header is marked as failed verification.
Best practices
Scope webhooks narrowly
Use entityId to tie a time activity webhook to one Time Clock when only that clock matters, and create separate webhooks for each feature you consume, each with its own secret.
Order events yourself
Connecteam's chat guide says not to assume ordering. Use eventTimestamp and the createdAt and modifiedAt fields inside the payload, all in Unix seconds.
Tolerate unknown fields and events
Ignore fields and eventType values you don't recognise and answer 200, so new events never turn into failures.
Acknowledge fast and process asynchronously
Check the header, store the request, return 200, then do the work. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Connecteam sends unsigned JSON webhooks per feature, authenticated only by the secretKey it copies into x-webhook-secret. Always set the secret and compare it in constant time over HTTPS, branch on the feature before parsing, build idempotency keys from requestId, eventType and the entity, and answer within 10 seconds because only three retries follow a failure.
Hookdeck Event Gateway checks the header on every Connecteam request, records each event, and retries delivery so a payroll-critical clock-out isn't lost to an outage on your side.
Get started with Hookdeck for free and handle Connecteam webhooks reliably in minutes.