Skip to content

Webhooks

Enterprise feature

Webhooks are available on Enterprise plans. Compare plans to find the right fit for your team.

A webhook tells your own system the moment something happens in FSM Navigator. When a job is completed, for example, we send an HTTPS POST with the job's details to an address you choose. Your accounting tool, data warehouse or Zapier workflow can act on it straight away instead of checking for changes.

You can set webhooks up in two ways:

  • From the Webhooks page in your dashboard — for your own receiving system.
  • Through the API (REST hooks) — this is how Zapier and similar apps subscribe for you.

Who can manage webhooks

The account Owner and Managers can open the Webhooks page from the side menu. Other roles do not see it.

Add an endpoint

  1. Open Webhooks from the side menu.
  2. Select Add endpoint.
  3. Enter the address that will receive events. It must be a public https:// address — addresses on a private or internal network are refused.
  4. Optionally add a description, such as "ERP sync".
  5. Tick the events you want, then select Save endpoint.
  6. Copy the signing secret that appears. It is shown only once. Store it in the receiving system so it can check that each request really came from us.

Use Send test at any time to send a ping event and see whether your endpoint accepts it.

Events you can subscribe to

Event When it is sent
job.created A new job is created
job.updated A job's details (name, description, schedule, priority and similar), its required skills, its linked assets or its required parts change
job.status_changed A job moves to a new status, for example Assigned → In-Transit
job.completed A job is completed (sent in addition to job.status_changed)
job.deleted A job is deleted (it can still be restored)
job.restored A deleted job is restored
invoice.paid An invoice becomes fully paid
customer.created A new customer is added
service_request.created A customer or website visitor submits a service request

invoice.paid and service_request.created are available to endpoints added on the Webhooks page. API subscriptions (REST hooks) can use the job and customer events.

What we send

Every request is a JSON body like this:

{
  "id": "0192f0c4-7b1e-7cc3-9a51-3c2f6d0e8a41",
  "type": "job.completed",
  "api_version": "2026-10-01",
  "occurred_at": "2026-10-01T14:32:07.118Z",
  "source": "mobile",
  "data": {
    "object": {
      "job_id": 12345,
      "job_number": "JOB-1042",
      "job_title": "Furnace tune-up",
      "job_status": "Completed",
      "customer": { "customer_id": 881, "customer_name": "Acme HVAC" }
    },
    "previous": { "status": "In-Progress" },
    "version": 7
  }
}
  • data.object has the same fields as the matching Jobs API or Customers API response, read at the moment we send it. Invoice amounts are whole cents. If the record no longer exists, data.object holds only its id and "deleted": true.
  • id is the event id. It is the same on every retry and on a manual resend.
  • source tells you where the change came from: ui (web dashboard), mobile (mobile app), api or api_key:<id> (Enterprise API), scheduled (recurring jobs and other scheduled work), auto_assignment_rebalance (automatic assignment), portal (customer portal) or booking (online booking).

Each request also carries these headers:

Header Meaning
X-FSM-Event-Id The event id — use it to ignore duplicates
X-FSM-Event-Type The event type
X-FSM-Delivery-Id This particular delivery attempt
X-FSM-Timestamp When we signed the request (Unix seconds)
X-FSM-Signature t=<timestamp>,v1=<signature>

Verify the signature

The signature is an HMAC-SHA256 of the timestamp, a full stop, and the raw request body, using your signing secret as the key. Compare it with the v1= value before trusting the request, and reject requests whose timestamp is more than five minutes old.

$header = $_SERVER['HTTP_X_FSM_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $header), $parts);
$expected = hash_hmac('sha256', $parts['t'] . '.' . $body, $signingSecret);
$valid = abs(time() - (int) $parts['t']) <= 300
    && preg_match_all('/v1=([0-9a-f]{64})/', $header, $m)
    && in_array(true, array_map(fn ($s) => hash_equals($expected, $s), $m[1]), true);

When you create a new secret with New secret, we sign with both the new and the old secret for 24 hours, so the header carries two v1= values. Accept the request if either one matches.

Delivery, retries and ordering

  • Answer with any 2xx status within 10 seconds to confirm delivery. We read at most 64 KB of your response.
  • If your endpoint times out, cannot be reached, or answers 408, 429 or 5xx, we try again with growing gaps, up to eight attempts over about 15 hours, before giving up.
  • Redirects are not followed. Other 4xx answers are treated as final.
  • Answering 410 Gone tells us to stop: an API subscription is removed and a dashboard endpoint is switched off.
  • The same event can arrive more than once. Use X-FSM-Event-Id to skip ones you have already handled.
  • Events can arrive out of order. Use occurred_at and data.version to ignore an older update that arrives after a newer one.

If an endpoint keeps failing, it is switched off and the Owner and Managers receive an email and an in-app notification. Fix the receiving system, then select Turn on — we send a test event first and only turn it back on if it is accepted.

Delivery log

The Delivery log tab lists every delivery with its status, response code and timing. Filter by endpoint or status, and use Send again to resend an event with the same event id. The log keeps 30 days of history.

REST hooks for Zapier and other apps

Apps that follow the REST Hooks pattern subscribe through the API with an API key that has the webhooks:manage scope, plus the read scope for the event (jobs:read for job events, customers:read for customer.created). See API authentication for creating keys.

curl -X POST "https://app.example.com/api/v1/hooks.php" \
  -H "X-API-Key: fsm_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"target_url": "https://hooks.example.com/catch/123", "event": "job.completed"}'

Answers 201 with {"id": 42, "event": "job.completed", "secret": "whsec_..."}.

curl -X POST "https://app.example.com/api/v1/hooks.php?action=unsubscribe&id=42" \
  -H "X-API-Key: fsm_live_xxxxxxxx"
curl "https://app.example.com/api/v1/hooks.php" -H "X-API-Key: fsm_live_xxxxxxxx"
curl "https://app.example.com/api/v1/hooks.php?action=sample&event=job.completed" \
  -H "X-API-Key: fsm_live_xxxxxxxx"

Returns data as a one-item array with an example event built from your most recent job or customer, for apps that need sample data while you set up a workflow.

A key can only see and remove the subscriptions it created. Revoking the key removes its subscriptions. These subscriptions also appear, read-only, on the Webhooks page under Connected through the API.