Gareth Wilson Gareth Wilson

Guide to Connecteam Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationWebhook API: POST /settings/v1/webhooks, with settings.write
ScopeOne featureType per webhook, optionally narrowed by entityId
Event selectioneventTypes array. A PUT replaces the whole list
AuthenticationOptional secretKey, sent unchanged in x-webhook-secret
SignatureNone
MethodPOST, as documented in the Chat guide
BodyJSON. Envelope shape varies by feature
VersionswebhookVersion, default 1. Shift webhooks also have 2
Response window10 seconds. Any 2xx is success
RetriesUp to retryLimit (default 3) after the first attempt, with no fixed delay. 404 is never retried
Source IPsNot published

Common events

Connecteam documents 52 event types across its six features:

FeatureEvents
usersuser_created, user_updated, user_deleted, user_archived, user_restored, user_promoted, user_demoted
formsform_submission, form_submission_edited, manager_field_updated
time_activityclock_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_schedulershift_created, shift_updated, shift_deleted, availability_status_created, availability_status_deleted
taskstask_published, task_completed
chatmessage_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_created and user_updated carry the full user under data, with a userId field. The other user events carry only { "id": ... }.
  • Time Activity events are flat. userId, timeClockId and timeActivity sit at the root with webhookVersion, not under data, and empty optional fields are left out rather than sent as null.
  • Older time activity and chat events also send evnetTimestamp, a deprecated misspelling of eventTimestamp. Read eventTimestamp.
  • Shift webhooks with webhookVersion 1 send one call per assigned user. Version 2 sends one call per shift with all assignedUserIds, 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-secret on 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 2xx within 10 seconds and do the work afterwards.
  • Never return 404 from 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, eventType and 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 activityType or the webhook's feature before reading fields.
  • Treat missing keys as absent, since Time Activity omits empty optional fields.
  • Pin webhookVersion explicitly 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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.