FieldCamp API Webhooks: Configure and Verify Events
Create FieldCamp webhook endpoints, subscribe to the supported event set, verify HMAC signatures, update endpoints, and inspect delivery attempts.
FieldCamp webhooks send an HTTPS POST request to your endpoint when a subscribed event occurs. Every delivery includes an HMAC-SHA256 signature, the event name, and a delivery ID.
Before you start, create an API key with the webhooks:manage scope. See FieldCamp API authentication for supported headers and scope behavior.
Supported events
Webhook endpoints can subscribe to these seventeen event names:
| Resource | Events |
|---|---|
| Client | client.created, client.updated, client.deleted |
| Job | job.created, job.updated, job.status_changed, job.deleted |
| Visit | visit.status_changed, visit.updated |
| Invoice | invoice.created, invoice.updated, invoice.paid, invoice.deleted |
| Custom objects | object.created, object.updated, object.stage_changed, object.deleted |
The four object.* events are generic across every custom object your account
defines — the payload carries the object's slug, so filter on it if you only care
about one type. See Custom Objects.
Subscribing needs one scope for all of them: webhooks:manage. There is no
per-resource scope on a subscription — a key with webhooks:manage can subscribe
to any event in this table regardless of its read scopes.
FieldCamp rejects an endpoint request containing any other event name.
Today these fire on API writes, not on app activity
For the client, job, visit and invoice events, the emit sites are the
/api/v1/... routes. So a client your integration creates over the API fires
client.created, but a client someone adds in the web app does not — and the same
applies to a job rescheduled on the dispatch calendar or an invoice paid in the
app. The object.* events are the exception: those fire from the app's own
custom-object writes and from workflow automations as well as from the API.
Until the other events are wired into the app's write paths, pair your
subscriptions with a periodic since sweep so activity that originated in the UI
still reaches your system.
Use client, job, and invoice resource identifiers when reconciling webhook activity with API data.
Create a webhook endpoint
Your receiving URL must:
- begin with
https://; - accept
POSTrequests with a JSON body; - verify the FieldCamp signature before processing the body; and
- return a
2xxresponse within 30 seconds.
Create the endpoint with POST /api/v1/webhooks:
curl -X POST https://api.fieldcamp.ai/api/v1/webhooks \
-H "X-Api-Key: $FIELDCAMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/webhooks/fieldcamp",
"events": ["job.status_changed", "invoice.paid"],
"description": "Operations sync",
"isActive": true,
"metadata": {"environment": "production"}
}'| Field | Required | Behavior |
|---|---|---|
url | Yes | Must be a string beginning with https://. |
events | Yes | Must be a non-empty array containing only supported event names. |
description | No | Stores a label or note for the endpoint. |
isActive | No | Defaults to true. Inactive endpoints do not receive events. |
metadata | No | Stores your own JSON metadata with the endpoint. |
FieldCamp generates the signing secret. Do not send a secret in the create request. The response returns the generated secret once, together with the endpoint record.
Store the returned webhook secret immediately. It is omitted from later list and detail responses.
List and inspect endpoints
Use these routes with a key that has webhooks:manage:
| Method and route | Purpose |
|---|---|
GET /api/v1/webhooks | List endpoints for the current FieldCamp account. |
GET /api/v1/webhooks/{id} | Get one endpoint and its delivery totals for the previous 24 hours. |
GET /api/v1/webhooks/{id}/deliveries | List delivery records for one endpoint. |
The list routes accept page and limit. The default limit is 30 and the maximum is 100.
Update or disable an endpoint
Use PUT /api/v1/webhooks/{id}. The accepted fields are url, events, description, isActive, and metadata.
curl -X PUT https://api.fieldcamp.ai/api/v1/webhooks/$WEBHOOK_ID \
-H "X-Api-Key: $FIELDCAMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["job.updated", "job.status_changed"],
"isActive": false
}'- Set
isActivetofalseto stop new deliveries without deleting the endpoint. - Set it back to
trueto resume matching new events. - If you change
url, FieldCamp generates a new signing secret and includes it in that update response. Replace the old secret in your receiver before relying on deliveries to the new URL.
The update method is PUT, not PATCH.
Delete an endpoint
Use DELETE /api/v1/webhooks/{id}. Deleting an endpoint also deletes its stored delivery records and cannot be used as a temporary pause. Use isActive: false when you want to keep the configuration and history.
Verify the signature
FieldCamp sends these headers with each delivery:
| Header | Value |
|---|---|
Content-Type | application/json |
X-FieldCamp-Signature | Lowercase hexadecimal HMAC-SHA256 signature. |
X-FieldCamp-Event | The subscribed event name. |
X-FieldCamp-Delivery | The unique delivery record ID. |
The signature is HMAC-SHA256 over the exact JSON request body using the endpoint secret. It does not include a timestamp prefix or v1= wrapper.
import crypto from 'node:crypto';
export function verifyFieldCampWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const expectedBytes = Buffer.from(expected, 'hex');
const receivedBytes = Buffer.from(signature || '', 'hex');
return (
expectedBytes.length === receivedBytes.length &&
crypto.timingSafeEqual(expectedBytes, receivedBytes)
);
}Pass the raw request-body string to the verifier before changing or re-serializing it. Reject the request when the signature is missing or does not match.
Use X-FieldCamp-Delivery as an idempotency key in your receiver so a retry does not perform the same downstream action twice.
Handle delivery attempts
FieldCamp treats any non-2xx response, network error, or 30-second timeout as a failed attempt. A successful attempt sets the delivery status to success; a failed one sets it to retrying and records the time the next attempt is due, on this schedule:
| Attempt | Timing |
|---|---|
| 1 | Initial delivery |
| 2 | 1 minute after the first failure |
| 3 | 5 minutes after the second failure |
| 4 | 30 minutes after the third failure |
| 5 | 2 hours after the fourth failure |
Build your receiver as if there is one attempt. The retry schedule above is
what a delivery is stamped with, but the sweep that would pick those retries back
up is not running today, so in practice a delivery that fails once stays in
retrying and is not tried again. Wiring that sweep is on our list.
Two consequences for your integration:
- Make your endpoint respond
2xxfast and do the real work asynchronously, so a slow downstream system cannot cost you the event. - Treat webhooks as a low-latency signal, not as your source of truth. Keep a
periodic reconciliation pass using
sinceon Jobs, Clients, Visits and Invoices, orupdated_sinceon Custom Objects. You can see what failed at any time withGET /api/v1/webhooks/{id}/deliveries?status=retrying.
There is no manual replay route, and an endpoint is never disabled automatically after repeated failures. Recover missed changes through the relevant resource API after fixing your receiver.
Inspect deliveries
Request:
GET /api/v1/webhooks/{id}/deliveriesThe response lists:
- delivery
idandevent; statusand number ofattempts;- last
responseCodeorerrorMessage; - request
duration; createdAtandlastAttemptAt.
You can filter by:
status=pending|success|failed|retryingevent=<supported-event-name>page=<number>limit=<1-100>
The delivery listing does not return the original payload, a response body, or a replay control.
Endpoints
curl -X GET "https://example.com/api/v1/webhooks"{}curl -X POST "https://example.com/api/v1/webhooks"{}curl -X GET "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"{}curl -X PUT "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"{}curl -X DELETE "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"{}/api/v1/webhooks/{id}/deliveriesPath Parameters
Response Body
application/json
application/json
curl -X GET "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0/deliveries"{}Troubleshooting
Signature verification fails
- Verify the bare hexadecimal value from
X-FieldCamp-Signature. - Hash the exact received body, not a parsed and re-serialized object.
- Confirm you stored the secret returned by the latest create or URL-change response.
- Do not prepend a timestamp or parse a
v1=value; those are not part of this contract.
The endpoint receives no events
- Confirm
isActiveistrue. - Confirm the event name is included in the endpoint's
eventsarray. - Confirm the action occurred in the same FieldCamp account that owns the API key and endpoint.
Deliveries remain failed
Inspect the delivery's response code and error message, correct the receiving service, then reconcile any missed resource changes through the API.
Related articles
FieldCamp API Rate Limits and Throttling Headers
Understand FieldCamp API rate limits, the 60 RPM sliding window, throttling headers, and how to handle 429 errors in production integrations.
FieldCamp Webhook Events Catalog: Event Types and Payloads
Reference for every FieldCamp webhook event, including sample payloads, required scopes, and tips for building reliable webhook receivers.