ServiceM8 Webhook API: Understanding Form Response Payloads
A technical breakdown of ServiceM8's new Event Webhook for form responses, including payload structure, headers, and related object data — based on real-world testing and a line-by-line comparison against ServiceM8's official developer docs.
ServiceM8 Webhook API: Understanding Form Response Payloads
ServiceM8 has introduced a new Event Webhook for business events, and form.response_created is one of the events it supports. It's a significant addition for anyone building real-time integrations on top of the platform. Instead of polling for changes or relying on delayed syncs, you can now receive form response data the moment it's submitted, complete with the related job context.
The official docs confirm the event exists and tell you how to subscribe to it. What they don't yet cover is the shape of the payload you actually receive. This article cross-references everything against ServiceM8's API Reference and Guides, so you can see exactly what's officially documented, what's documented elsewhere but not linked from the webhook docs, and what we've only been able to confirm by testing in production.
Last updated: 22 June 2026. ServiceM8 may publish updated documentation after this date. The details below reflect what we observed when testing the form.response_created event, plus a side-by-side check against the published schemas.
Two Webhook Systems, Easy to Confuse
Before getting into the payload, it's worth flagging something the docs don't call out clearly: ServiceM8 currently has two separate webhook subscription mechanisms, and it's easy to wire up the wrong one.
- Object Webhook Subscriptions (
/webhook_subscriptions/object) — the original mechanism. You subscribe to specific fields on an object type (e.g.job,company). When a subscribed field changes, you get a minimal payload:{object, entry: [{changed_fields, time, uuid}], resource_url}. It does not include the record itself, you have to fetch it. - Event Webhook Subscriptions (
/webhook_subscriptions/event) — the new mechanism this article is about. You subscribe to a named business event (job.created,job.completed,form.response_created, and over 20 others) and receive a richer payload with the record data included.
The Webhooks Overview guide still only documents the first, older mechanism. If you've read that guide and expected entry/changed_fields, that's why the real payload looks nothing like it.
The Event Webhook Subscription reference also notes that Event Webhooks are a new capability and were being rolled out gradually, with full availability targeted by 18 September. If you're not seeing events fire yet on an older account, that's a likely reason.
Event Types
form.response_created is one of more than 20 supported business events, not the only one. The full list (per the official reference) includes things like job.created, job.completed, job.quote_accepted, staff.clocked_on, and company.created. This article focuses on the one most relevant to form integrations:
| Event Type | Description |
|---|---|
form.response_created | Fired when a form response is submitted against a job |
Delivery Format
Webhooks are delivered as HTTP POST requests to your configured endpoint.
Headers
⚠️ Not officially documented. We could not find either of these headers mentioned anywhere in the current ServiceM8 docs. The behaviour below is based on observed traffic only, treat it as unconfirmed until ServiceM8 publishes it.
| Header | Description |
|---|---|
x-addon-unique-id | A unique identifier for your add-on or webhook configuration, useful for routing |
x-account-uuid | The ServiceM8 account UUID that triggered the webhook |
The payload body is Content-Type: application/json.
Body Structure
⚠️ Not officially documented. This top-level envelope doesn't appear in any current guide or reference page. It's our own description of what we received on the wire.
| Field | Type | Description |
|---|---|---|
id | string | Unique webhook event identifier (prefixed smwe_) |
type | string | The event type (e.g. form.response_created) |
data | object | The form response data itself |
related | object | Related objects, currently includes the job the form is attached to |
The data Object (Form Response)
This is the part where the docs and reality partially overlap. Every field below matches the officially published FormResponse object schema, used for the REST GET /formresponse/{uuid}.json endpoint. ServiceM8 just doesn't tell you, anywhere in the webhook docs, that the data object you receive on this event is the same shape:
| Field | Type | Description | Documented? |
|---|---|---|---|
uuid | string | The form response UUID | Yes (FormResponse schema) |
form_uuid | string | The UUID of the form template that was submitted | Yes |
regarding_object | string | What the form relates to, currently always job | Yes |
regarding_object_uuid | string | The UUID of the related object (e.g. the job UUID) | Yes |
staff_uuid | string | The staff member who submitted the form (may be empty if submitted via customer portal) | Yes |
form_by_staff_uuid | string | The staff member who filled out the form on behalf of someone else | Yes |
field_data | string (JSON) | A JSON-encoded string containing an array of form field responses | Partially. Typed as string in the schema, but nowhere does it say you need to JSON.parse() it |
edit_date | datetime | When the response was last edited | Yes |
timestamp | datetime | When the response was created | Yes |
active | integer | 1 if the response is active, 0 if deleted | Yes |
document_attachment_uuid | string | UUID of an attached document, if any | Yes |
asset_uuid | string | UUID of a linked asset, if any | Yes |
So the correction to make here, versus how this is often reported (including in an earlier draft of this article): these fields aren't undocumented, they're documented in the wrong place. If you only read the webhook guides, you'd never find them. If you happen to check the FormResponse REST reference, they're all there.
The field_data Array
field_data is a JSON-encoded string. Once parsed, it is an array of objects, each representing a single form field. None of this internal structure is documented anywhere — not in the FormResponse schema (which just says string), and not in the Form Field schemas either:
| Field | Type | Description |
|---|---|---|
UUID | string | Unique identifier for this specific field response |
FieldType | string | The type of field, e.g. Text, Multiple Choice, Date, Photo |
Response | string | The actual response value |
SortOrder | integer | The order of this field in the form (1-based) |
Question | string | The internal question key (prefixed form_) |
Known FieldType Values
⚠️ No enum exists in the docs. We checked the Create Form Field and related Form Field schemas directly:
field_data_jsonis typed as an opaquestringwith no enumerated list of supported question types anywhere in the published spec. Everything below is from observed traffic only.
From our testing, the following field types are used:
Text— Free text inputMultiple Choice— Single-select or multi-select optionsDate— Date picker response (formatted asYYYY-MM-DD HH:mm:ss)Photo— A photo upload; the response value is a UUID referencing the uploaded image
Other types (Checkbox, Signature, Dropdown, Number, etc.) are expected to exist on the Forms feature but haven't been confirmed by us in webhook traffic yet.
The related Object
⚠️ Not officially documented. We could not find this object, or any of the job fields below, on any published schema, guide, or reference page. This entire section is reconstructed from production traffic.
The related object contains the contextual data associated with the form response. Currently, only the job is included.
Job Object
| Field | Type | Description |
|---|---|---|
uuid | string | Job UUID |
generated_job_id | string | Human-readable job number (e.g. 778) |
status | string | Current job status (Work Order, Quote, Invoice, Completed, etc.) |
job_address | string | Full job address |
billing_address | string | Billing address |
job_description | string | Description of the work |
date | date | Scheduled job date |
work_order_date | datetime | When the work order was created |
completion_date | datetime | When the job was completed (may be 0000-00-00 00:00:00 if not completed) |
company_uuid | string | The client company UUID |
geo_is_valid | integer | Whether geocoding was successful (1 or 0) |
lat | number | Latitude |
lng | number | Longitude |
geo_country | string | Country (e.g. Australia) |
geo_state | string | State (e.g. NSW) |
geo_city | string | City/suburb (e.g. Cherrybrook) |
geo_postcode | string | Postcode (e.g. 2126) |
geo_street | string | Street name |
geo_number | string | Street number |
active | integer | Whether the job is active |
badges | string | Any badges on the job |
total_invoice_amount | string | Total invoice amount |
payment_processed | integer | Payment processed flag |
payment_received | integer | Payment received flag |
category_uuid | string | Job category UUID (may be empty) |
queue_uuid | string | Job queue UUID (may be empty) |
purchase_order_number | string | Purchase order number |
quote_sent | boolean | Whether the quote has been sent |
invoice_sent | boolean | Whether the invoice has been sent |
If you've used the Job REST reference before, most of these fields will look familiar. It appears the related.job object is, in effect, the full Job record bundled in for free. ServiceM8 just hasn't said so anywhere.
What We Observed During Testing
A few things stood out once we had real webhook traffic flowing, none of which are mentioned in the docs:
Duplicate deliveries are possible. We observed the same form response payload delivered more than once, with a different webhook event ID (smwe_*) each time. Your integration needs to be idempotent. Deduplicate by the form response uuid in the data object, not by the webhook event id. The Event Webhook Subscription reference only states that your callback must return a 2xx response within 10 seconds or risk deactivation, it says nothing about delivery guarantees.
The field_data is a string, not an object. Easy to miss given how it's labelled in some places. You'll need to JSON.parse() it before you can iterate over the fields.
Job data is included on every form response. You get the full job context (address, geolocation, status, amounts) alongside the form data, so you don't need a separate API call to look up the job. This is genuinely useful, it's just not written down anywhere yet.
Zero-value timestamps. Several datetime fields use 0000-00-00 00:00:00 as a "not set" sentinel. Make sure your code handles this rather than attempting to parse it as a valid date.
Photo responses are UUIDs, not URLs. When a Photo field type is submitted, the response value is a UUID. Resolving this to an actual image URL requires a separate API call or lookup.
What's Actually Missing From the Documentation
To be precise about this, rather than just saying "the docs are incomplete":
| Gap | Status |
|---|---|
Full list of supported FieldType values | Confirmed missing — no enum anywhere in the Form Field schemas |
data object field reference inside webhook context | Fields exist on the FormResponse schema, but aren't linked from the webhook docs |
field_data requires JSON.parse() | Type is documented as string, but the parsing requirement isn't called out |
related object structure (job enrichment) | Confirmed missing — no schema published anywhere |
| Idempotency / duplicate delivery guidance | Confirmed missing — only timeout/deactivation behaviour is documented |
x-addon-unique-id / x-account-uuid headers | Confirmed missing from all guides and references |
| Zero-value datetime sentinel convention | Confirmed missing |
| Error response formats and retry behaviour | Not found in current docs |
This is understandable. Event Webhooks are a new, staged-rollout feature, and documentation for genuinely new capabilities often lags the engineering work. If you're building on this today, test thoroughly and treat every field as potentially nullable or absent.
Suggested Architecture for Handling Webhooks
If you're building a ServiceM8 webhook integration for form responses, here's a recommended approach:
- Set up a webhook endpoint that accepts POST requests and records (but doesn't yet rely on) the
x-addon-unique-idandx-account-uuidheaders - Parse and deduplicate — store
data.uuidand skip anything you've already processed - Parse
field_data— decode the JSON string and map eachQuestionkey to a usable field name - Enrich with job context —
related.jobhas most of what you need for typical workflows, without an extra API call - Handle missing values defensively — expect empty strings, zero UUIDs, and
0000-00-00 00:00:00dates - Log and monitor — since the feature is new and partially undocumented, watch for payload shapes you haven't seen before
Conclusion
ServiceM8's Event Webhook for form responses is a genuinely useful addition, real-time, event-driven data beats polling every time. But right now there's a real gap between what's published and what the platform actually sends. Some of that gap is fields documented in the wrong place (the FormResponse schema, disconnected from the webhook docs). Some of it is genuinely undocumented (the related object, the headers, idempotency behaviour, the sentinel date format). Knowing which is which matters if you're deciding how much to rely on unconfirmed behaviour in production.
We've reported the confirmed gaps directly to ServiceM8 and will update this article as their documentation catches up.
Need a Hand Building on This?
We're Proanalytica Technologies, a Sydney-based consultancy that builds web, cloud, mobile, and AI automation solutions, including ServiceM8 integrations like this one. If you're a developer or business working with ServiceM8's API and want to talk through webhook architecture, idempotency handling, or wiring this up to your own systems, get in touch:
Jayden Lee
Founder of Proanalytica Technologies. Machine learning engineer and software developer based in Sydney, NSW. Helping Greater Sydney small businesses build better digital infrastructure.
Need help with your Sydney business?
From web design and WordPress maintenance to ServiceM8 setup and AI automation — we work with Greater Sydney SMBs.
Get in Touch