FieldCamp

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:

ResourceEvents
Clientclient.created, client.updated, client.deleted
Jobjob.created, job.updated, job.status_changed, job.deleted
Visitvisit.status_changed, visit.updated
Invoiceinvoice.created, invoice.updated, invoice.paid, invoice.deleted
Custom objectsobject.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 POST requests with a JSON body;
  • verify the FieldCamp signature before processing the body; and
  • return a 2xx response 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"}
  }'
FieldRequiredBehavior
urlYesMust be a string beginning with https://.
eventsYesMust be a non-empty array containing only supported event names.
descriptionNoStores a label or note for the endpoint.
isActiveNoDefaults to true. Inactive endpoints do not receive events.
metadataNoStores 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 routePurpose
GET /api/v1/webhooksList 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}/deliveriesList 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 isActive to false to stop new deliveries without deleting the endpoint.
  • Set it back to true to 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:

HeaderValue
Content-Typeapplication/json
X-FieldCamp-SignatureLowercase hexadecimal HMAC-SHA256 signature.
X-FieldCamp-EventThe subscribed event name.
X-FieldCamp-DeliveryThe 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:

AttemptTiming
1Initial delivery
21 minute after the first failure
35 minutes after the second failure
430 minutes after the third failure
52 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 2xx fast 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 since on Jobs, Clients, Visits and Invoices, or updated_since on Custom Objects. You can see what failed at any time with GET /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}/deliveries

The response lists:

  • delivery id and event;
  • status and number of attempts;
  • last responseCode or errorMessage;
  • request duration;
  • createdAt and lastAttemptAt.

You can filter by:

  • status=pending|success|failed|retrying
  • event=<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

GET/api/v1/webhooks

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/webhooks"
{}
POST/api/v1/webhooks

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/webhooks"
{}
GET/api/v1/webhooks/{id}

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"
{}
PUT/api/v1/webhooks/{id}

Path Parameters

id*string

Response Body

application/json

application/json

curl -X PUT "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"
{}
DELETE/api/v1/webhooks/{id}

Path Parameters

id*string

Response Body

application/json

application/json

curl -X DELETE "https://example.com/api/v1/webhooks/665f1c2ab3e4d5f6a7b8c9d0"
{}
GET/api/v1/webhooks/{id}/deliveries

Path Parameters

id*string

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 isActive is true.
  • Confirm the event name is included in the endpoint's events array.
  • 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.

On this page