Webhook step

Language: English

A webhook step sends an HTTP request to an external system each time the flow reaches it. Webhooks are the bridge between ActivityInfo and other tools: workflow platforms such as Power Automate, Zapier, n8n, or Pipedream, chat tools such as Google Chat or Slack, or your own services.

Configuration

Setting Description
Label The step's name, shown in the flow and in run history.
Webhook URL The URL the request is sent to. Must be an https:// URL.
Payload format The shape of the request body: ActivityInfo 4.0-compatible, Standard, or Custom.
Signing secret Optional. When generated, every request is signed so the receiver can verify it. See Signing.

Delivery

The request is an HTTP POST with Content-type: application/json. The connection and read timeouts are 15 seconds and redirects are not followed. A response with a 2xx status code marks the step successful; any other response, or a connection failure, causes the delivery to be retried.

All requests carry these headers, following the standard-webhooks convention:

Header Value
webhook-id A unique ID for the event, e.g. msg_cfbin3msbq939jk. Retries of the same event carry the same ID, so receivers can deduplicate.
webhook-timestamp The send time, in seconds since the epoch.
webhook-signature Present when a signing secret has been generated. See Signing.

Signing

Generating a signing secret lets the receiving system verify that a request genuinely comes from ActivityInfo and has not been tampered with. The signature follows the standard-webhooks symmetric scheme:

webhook-signature: v1,<base64 HMAC-SHA256 of "{webhook-id}.{webhook-timestamp}.{body}">

The signature is computed over the exact bytes of the request body, whatever the payload format. The secret is shown once when generated — copy it into the receiving system at that moment. Changing the webhook URL clears the secret; save the new URL first, then generate a new secret for it.

Payload formats

The examples below all come from the same event: a record in a Patient Registration form was edited, changing the Status field from "Under treatment" to "Recovered". The form's fields carry codes (NAME, AGE, STATUS, ...) — we strongly recommend assigning codes to your fields, since they identify fields in the payloads and templates below far more readably than field IDs, and survive relabeling.

The form has one field of each common type: a serial number (NUMBER), text (NAME), a quantity (AGE), a date (REGDATE), a month (MONTH), a week (WEEK), a single selection (STATUS), a multiple selection (SYMPTOMS), a reference to a Clinic form (CLINIC), a multiple reference (NEARBY), a geographic point (LOCATION), and a calculated field (ADULT).

ActivityInfo 4.0-compatible

The exact payload format of ActivityInfo 4.0's webhooks, kept for the integrations built against it — including the Power Automate connector. Automations migrated from 4.0, and connections made through Power Automate, use this format; new webhook steps default to Standard. See From ActivityInfo 4.0 webhooks to automations for the background.

The body describes the event: data.update carries only the fields changed by the event (all fields for an added record), while data.previous — present for edits and deletions — is the record's full stored state before the change. Fields are keyed by code (or field ID when the field has no code). The event type is record.added, record.edited, or record.deleted.

Because the body is built from the record's stored values, calculated fields are not included — they are computed on read, not stored, so they never appear in this format. A consumer that needs a calculated value must either recompute it or use the Standard or Custom format, which evaluate calculated fields like any other.

{
  "type": "record.edited",
  "timestamp": "2026-08-02 13:30:00 +0200",
  "id": "cfbin3msbq939jk",
  "data": {
    "update": {
      "formId": "cabwlbemsbq938l5",
      "formLabel": "Patient Registration",
      "recordId": "cuusah2msbq938xi",
      "parent": null,
      "deleted": false,
      "time": "2026-08-02 13:30:00 +0200",
      "fields": {
        "STATUS": "Recovered"
      }
    },
    "previous": {
      "formId": "cabwlbemsbq938l5",
      "formLabel": "Patient Registration",
      "recordId": "cuusah2msbq938xi",
      "parent": null,
      "time": "2026-07-30 11:00:00 +0200",
      "fields": {
        "NAME": "Amina Yusuf",
        "AGE": 34,
        "REGDATE": "2026-07-30",
        "MONTH": "2026-07",
        "WEEK": "2026W31",
        "STATUS": "Under treatment",
        "SYMPTOMS": ["Fever", "Cough"],
        "CLINIC": {
          "reference": {
            "recordId": "cfw5r5dmsbq938vh",
            "formId": "cprk1xdmsbq93893"
          }
        },
        "LOCATION": {
          "latitude": 0.9432,
          "longitude": 29.2154
        }
      }
    },
    "user": {
      "userName": "Amara Diallo",
      "userEmail": "amara.diallo@example.org",
      "userId": "ci5dxvbkq1v6ueg2"
    }
  }
}

Field values in this format:

Field type Value
Text, serial number, barcode Text
Quantity Number
Date, week, month Text, e.g. "2026-07-30", "2026W31", "2026-07"
Single selection The option's code if it has one, its label otherwise
Multiple selection Array of option codes/labels, in the form's option order
Reference {"reference": {"recordId": ..., "formId": ...}}
Multiple reference Array of "formId:recordId" strings
Geographic point {"latitude": ..., "longitude": ...}
Attachments Array of {"filename": ..., "mimetype": ..., "url": ...} with a download URL
Calculated Not included — calculated fields are computed on read, not stored

For a deletion (record.deleted), update.deleted is true, update.fields is empty, and previous carries the record's last state. Sub-form records carry a parent object with parentRecordId, parentFormId, and parentFormLabel in place of null.

Standard

The trigger's step data serialized as JSON — the same single row, laid out exactly as formulas see it. Every field of the form is present on every event (blank fields as null, so the payload's shape is stable), keyed by field code (or label when the field has no code). The _previous, _user, and _trigger structures appear under the same reserved names formulas use.

{
  "_id": "cuusah2msbq938xi",
  "NUMBER": null,
  "NAME": "Amina Yusuf",
  "AGE": 34,
  "REGDATE": "2026-07-30",
  "MONTH": "2026-07",
  "WEEK": "2026W31",
  "STATUS": {
    "_id": "s2",
    "label": "Recovered"
  },
  "SYMPTOMS": [
    { "_id": "y1", "label": "Fever" },
    { "_id": "y2", "label": "Cough" }
  ],
  "CLINIC": {
    "_id": "cfw5r5dmsbq938vh",
    "label": "Mangina Health Post"
  },
  "NEARBY": [
    { "_id": "cfw5r5dmsbq938vh", "label": "Mangina Health Post" },
    { "_id": "c7ptg4xmsbq938wj", "label": "Beni Reference Hospital" }
  ],
  "LOCATION": {
    "LATITUDE": 0.9432,
    "LONGITUDE": 29.2154,
    "ACCURACY": null
  },
  "ADULT": true,
  "_lastEditTime": "2026-08-02T11:30:00Z",
  "_previous": {
    "_id": "cuusah2msbq938xi",
    "NUMBER": null,
    "NAME": "Amina Yusuf",
    "AGE": 34,
    "REGDATE": "2026-07-30",
    "MONTH": "2026-07",
    "WEEK": "2026W31",
    "STATUS": {
      "_id": "s1",
      "label": "Under treatment"
    },
    "SYMPTOMS": [
      { "_id": "y1", "label": "Fever" },
      { "_id": "y2", "label": "Cough" }
    ],
    "CLINIC": {
      "_id": "cfw5r5dmsbq938vh",
      "label": "Mangina Health Post"
    },
    "NEARBY": [
      { "_id": "cfw5r5dmsbq938vh", "label": "Mangina Health Post" },
      { "_id": "c7ptg4xmsbq938wj", "label": "Beni Reference Hospital" }
    ],
    "LOCATION": {
      "LATITUDE": 0.9432,
      "LONGITUDE": 29.2154,
      "ACCURACY": null
    },
    "ADULT": true,
    "_lastEditTime": "2026-07-30T09:00:00Z"
  },
  "_user": {
    "name": "Amara Diallo",
    "email": "amara.diallo@example.org",
    "id": "ci5dxvbkq1v6ueg2"
  },
  "_trigger": {
    "type": "RECORD_EDITED",
    "time": "2026-08-02T11:30:00Z"
  }
}

Field values in this format:

Field type Value
Text, serial number, barcode Text, null when blank
Quantity Number
Date, week, month Text, e.g. "2026-07-30", "2026W31", "2026-07"
Single selection {"_id": <option id>, "label": <option label>}
Multiple selection Array of {"_id", "label"} objects
Reference {"_id": <record id>, "label": <record label>} — the same human-readable label the record has across the application
Multiple reference Array of {"_id", "label"} objects
Geographic point {"LATITUDE", "LONGITUDE", "ACCURACY"}
Attachments Array of {"_id", "filename", "mimetype"} objects
Calculated The formula's result
Date-and-time values (_lastEditTime, _trigger.time) ISO-8601 UTC, e.g. "2026-08-02T11:30:00Z"

_previous is present only for the edit trigger; _user entries are null for changes not attributable to a user.

Custom

You write the request body yourself, as a template with ${...} references — the same references the email step's templates use, evaluated against the trigger's step data. This is the format to use when the receiving system dictates the body's shape, such as posting a message directly into a Google Chat space:

{"text": "Patient ${NAME} at ${CLINIC.NAME} is now ${STATUS} (was ${_previous.STATUS}), updated by ${_user.name}"}

produces:

{"text": "Patient Amina Yusuf at Mangina Health Post is now Recovered (was Under treatment), updated by Amara Diallo"}

A reference can be a field code, a navigation through a reference field, a _previous/_user/_trigger entry, or any formula — the full formula language is available inline:

{"text": "Patient ${NAME} (${IF(AGE >= 18, 'adult', 'minor')}, age ${AGE}) is now ${STATUS}"}

produces:

{"text": "Patient Amina Yusuf (adult, age 34) is now Recovered"}

Use single quotes for text inside an inline formula, as in IF(AGE >= 18, 'adult', 'minor') — the formula language accepts both quote styles, and single quotes keep the surrounding JSON template readable.

Interpolated values are automatically JSON-escaped: quotes, backslashes, and line breaks in your data arrive as \", \\, and \n, so a template that is a JSON document remains valid JSON whatever the data contains. Everything outside ${...} is sent exactly as written — including backslash sequences like \n you type yourself — and a ${...} reference that cannot be resolved is left as written.

The template editor validates every ${...} reference against the trigger's step data and offers the same clickable field list as the filter and email steps.

See also

Next item
Explanation