Issue 326 Form Webhook Full Record - edmozley/freeitsm GitHub Wiki

๐Ÿช #326 / #327 A webhook's Full record never left on a form submission

GitHub #326 and #327, reported by pappas81 ยท Forms, Workflows ยท fixed in 5.2.0 (#2880)

What you saw

Two reports, one cause - pappas81 guessed as much ("it might be connected").

  • #326 - in a form. Add Send webhook to a form's What happens next โ†’ When submitted, submit the form: nothing arrives, and nothing is written to the webhook queue at all.
  • #327 - in a workflow. A workflow on A form submission is received with Send webhook in Full record format fails with: Action 0 (send_webhook): the full-record format needs a trigger that carries a record with a full object (ticket, change, problem, ...).

A Slack, Teams or Custom webhook on the same form worked. Only Full record failed.

What was wrong

Full record sends the whole record exactly as the REST API returns it. The engine does not build that itself: before the webhook is sent, enrichWithFullObjects() walks a list of record kinds it knows how to load, and for each one in the event's payload attaches the API's shape under <kind>.full:

// workflow/includes/engine.php - fullObjectLoaders(), 5.1.0
return [
    'ticket'   => $one('tickets.php', 'apiTicketSelect', ' WHERE t.id = ? LIMIT 1', fn($c, $r) => apiSerializeTicket($r)),
    'change'   => ...,
    ...
    'incident' => $one('service_status.php', 'apiServiceIncidentSelect', ' WHERE si.id = ? LIMIT 1', fn($c, $r) => apiSerializeIncident($c, $r)),
];   // no form submission

A form event's payload is form (id, name) and submission (id, email, the answers by label). submission was not on the list, so nothing got a .full, and buildWebhookRequest() refused:

if ($preset === 'full') {
    $full = null;
    foreach ($payload as $v) {
        if (is_array($v) && isset($v['full']) && is_array($v['full'])) { $full = $v['full']; break; }
    }
    if ($full === null) {
        throw new Exception('the full-record format needs a trigger that carries a record with a full object (...)');
    }

The refusal happens before webhookEnqueue(), which is why #326 saw an empty queue. The form's run did record it - Workflows โ†’ Run history has a failed run named Form: โ€ฆ - when submitted with that same error - but a form's actions are set up in the form designer, so nobody thinks to look in Workflows.

The REST API already had the shape - GET /forms/{id}/submissions/{id}, built by apiSerializeFormSubmission() in api/v1/resources/forms.php. It had simply never been registered with the webhook engine.

The fix

One line on the list, reusing the API's own SELECT and serialiser, exactly as the other eleven do:

// workflow/includes/engine.php - fullObjectLoaders(), 5.2.0
'submission' => $one('forms.php', 'apiFormSubmissionsSelect', ' WHERE s.id = ? LIMIT 1',
                     fn($c, $r) => apiSerializeFormSubmission($c, $r)),

and submission added to WorkflowEngine::FULL_OBJECT_KEYS - the same list without a database connection, which the merge-code picker reads - so {{submission.full}} is offered there too.

What a Full record from a form now sends:

{
  "id": 184,
  "form": { "id": 127, "title": "New starter request" },
  "submitted_by": { "id": 1, "name": "Administrator" },
  "submitted_at": "2026-11-13T21:41:46Z",
  "answers": [
    { "field_id": 269, "label": "Name", "field_type": "text", "value": "Karen Hall" },
    { "field_id": 270, "label": "Needs", "field_type": "checkboxes", "value": ["Laptop", "Phone"] }
  ]
}

Every answer is beside its field; checkboxes come as a list; a table question comes as rows with its column definitions (retired columns included and marked) - all from the API serialiser, so a webhook and an API call can never disagree.

It works on every event that carries a submission: A form submission is received, and the three catalogue request events (submitted for approval, approved, rejected), in a workflow or in a form's own What happens next. The error message now names form submissions and catalogue requests too.

Why it was not "the form's actions are broken"

The first thing checked was whether a form's own list ran at all. It did: a Custom and a Slack webhook from a form's What happens next both reached the queue. That narrowed it to the one format the two reports had in common.

๐Ÿ“ Files

File Change
workflow/includes/engine.php submission in fullObjectLoaders() and FULL_OBJECT_KEYS; the error lists it
workflow/help-webhooks.php the Full-record section lists submission.full
tests/form-webhook-full-record.php new, 9 checks

How it was proved

tests/form-webhook-full-record.php, on a throwaway install, through FormsService::submitForm() - the path the portal, the Forms module and the REST API all use. Nothing is sent: the deliveries are read and deleted at once, and their address is example.invalid.

  1. #326: a form whose What happens next has a Full record webhook - the run succeeds, one delivery is queued, and its body is identical to what GET /forms/{id}/submissions/{id} returns; checkboxes are a list.
  2. #327: a real, active workflow on A form submission is received with a Full record webhook - no error; the submission is queued, API-shaped.
  3. {{submission.full}} inside a Custom body embeds the same object.
  4. The merge-code list knows submission.

Control: the same test against 5.1.0's engine fails 8 of 9, with pappas81's exact error. tests/workflow-full-record.php (4) and tests/workflow-contains.php still pass.

What this means for you

  • Using Full record on a form, or on a form or catalogue request trigger? It works from 5.2.0. Nothing to set up; existing forms and workflows start sending.
  • Wondering whether a form's action ran? Look in Workflows โ†’ Run history - a form's own actions are logged there as Form: - when submitted.

Related

โš ๏ธ **GitHub.com Fallback** โš ๏ธ