Outgoing Webhooks

Outgoing webhooks send event notifications to your URL when things happen in Monotree. Configure each endpoint and the events it should subscribe to in the Open API → Outgoing Webhooks tab, or programmatically via the Manage Outgoing Webhooks API.

Available events

EventFires when
monotree.post.createdA post is created.
monotree.comment.createdA comment is added to a post.
monotree.comment.updatedA comment on a post is edited.
monotree.comment.deletedA comment on a post is deleted.
monotree.announcement.publishedAn announcement is published.
monotree.formresponse.createdA form response is submitted.
monotree.formresponse.updatedA form response's status, deadline, hidden/archived flag, or assignees change. Does not fire on initial creation (use formresponse.created) or for comments (use formresponse.comment.created). Filterable by form. Whistleblower forms never dispatch.
monotree.formresponse.comment.createdA public comment is added to a form response. Filterable by form. Whistleblower forms never dispatch. Internal staff notes do not trigger webhooks.
monotree.formresponse.comment.deletedA public comment on a form response is deleted. Filterable by form. Whistleblower forms never dispatch.
monotree.formresponse.alarm_triggeredA form response matched a configured field alarm rule. One delivery per matched rule, idempotent per response + rule pair. Filterable by form. Whistleblower forms never dispatch.
monotree.user.registeredA user accepts an invite and registers.
monotree.onboarding.completedA user completes onboarding.

Payload

Every delivery shares the same envelope — a unique id, the event name, the API version, a Unix timestamp, and an event-specific payload:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "event": "monotree.post.created",
  "version": "v1",
  "timestamp": 1711270800,
  "payload": {
    "id": 123,
    "body": "Post content here",
    "wall_id": 1,
    "wall_name": "General",
    "author": { "id": 1, "name": "John Doe", "email": "john@example.com", "avatar_url": "https://images.monotree.com/avatars/john-doe.jpg" },
    "created_at": "2026-03-22T10:00:00+00:00",
    "updated_at": "2026-03-22T10:00:00+00:00"
  }
}

Form response payload

monotree.formresponse.created and monotree.formresponse.updated carry the full form response, including every answered field:

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "event": "monotree.formresponse.created",
  "version": "v1",
  "timestamp": 1716190200,
  "payload": {
    "id": 4521,
    "form_id": 12,
    "form_title": "Daily Fridge Check",
    "is_anonymous": false,
    "status": "todo",
    "respondent": {
      "id": 88,
      "name": "Jane Doe",
      "email": "jane@example.com",
      "type": "employee",
      "avatar_url": "https://images.monotree.com/avatars/jane-doe.jpg",
      "groups": [
        { "id": 3, "name": "Kitchen", "type": "department", "created_at": "2026-01-10T08:00:00+00:00" }
      ],
      "hired_at": "2025-09-01",
      "is_away": false,
      "last_active_on": "2026-05-20",
      "registered_at": "2025-09-01T09:12:00+00:00",
      "created_at": "2025-09-01T09:00:00+00:00"
    },
    "department": { "id": 3, "name": "Kitchen", "type": "department", "created_at": "2026-01-10T08:00:00+00:00" },
    "field_responses": [
      {
        "value": "9",
        "field": { "id": 41, "title": "Fridge temperature", "type": "number" }
      },
      {
        "value": [
          { "id": 7, "title": "Needs cleaning" },
          { "id": 9, "title": "Restock required" }
        ],
        "field": { "id": 42, "title": "Issues found", "type": "choices" }
      },
      {
        "value": [
          {
            "id": 88,
            "name": "Jane Doe",
            "email": "jane@example.com",
            "type": "employee",
            "avatar_url": "https://images.monotree.com/avatars/jane-doe.jpg",
            "groups": [],
            "hired_at": "2025-09-01",
            "is_away": false,
            "last_active_on": "2026-05-20",
            "registered_at": "2025-09-01T09:12:00+00:00",
            "created_at": "2025-09-01T09:00:00+00:00"
          }
        ],
        "field": { "id": 43, "title": "Responsible person", "type": "users" }
      }
    ],
    "media": [
      { "id": 55, "type": "image", "mime_type": "image/jpeg", "filename": "fridge.jpg", "size": 182034, "url": "https://…/fridge.jpg", "width": 1280, "height": 960, "created_at": "2026-05-20T07:30:00+00:00" }
    ],
    "created_at": "2026-05-20T07:30:00+00:00",
    "updated_at": "2026-05-20T07:30:00+00:00"
  }
}

Notes on the top-level fields:

  • respondent is null when the response is anonymous.
  • department is null when the respondent has no department.
  • status is null unless the form uses response statuses — otherwise one of todo, in_progress, done.
  • media lists the files uploaded with the submission — attached to the response as a whole, not to a specific field (see Forms). Signed urls for anonymous-response files expire ~5 minutes after dispatch; re-fetch GET /formresponses/{id} for a fresh link.

The field_responses array holds one entry per answered field. The shape of value depends on field.type:

Field typevalue
text, date, datetime, time, number, rating, booleanThe submitted value.
choicesArray of selected choices, each { id, title }.
users, department_usersArray of selected users — same shape as respondent.

Form response comment payload

monotree.formresponse.comment.created and monotree.formresponse.comment.deleted carry the public comment:

{
  "id": "1f3a2b6c-2d4e-4a1b-9c8d-7e6f5a4b3c2d",
  "event": "monotree.formresponse.comment.created",
  "version": "v1",
  "timestamp": 1716190800,
  "payload": {
    "id": 901,
    "body": "Could you re-check the freezer reading?",
    "form_response_id": 4521,
    "form_id": 12,
    "author": {
      "id": 5,
      "name": "Sam Owner",
      "email": "sam@example.com",
      "type": "employee",
      "avatar_url": "https://images.monotree.com/avatars/sam-owner.jpg",
      "groups": [],
      "hired_at": "2024-02-01",
      "is_away": false,
      "last_active_on": "2026-05-20",
      "registered_at": "2024-02-01T09:00:00+00:00",
      "created_at": "2024-02-01T09:00:00+00:00"
    },
    "media": [
      { "id": 77, "type": "image", "mime_type": "image/jpeg", "filename": "freezer.jpg", "url": "https://cdn.monotree.dk/…/freezer.jpg", "width": 1200, "height": 800, "created_at": "2026-05-20T08:00:00+00:00" }
    ],
    "created_at": "2026-05-20T08:00:00+00:00",
    "updated_at": "2026-05-20T08:00:00+00:00"
  }
}

Notes:

  • Only public comments fire these events. Internal staff notes never dispatch.
  • media lists the comment's attachments (images, videos, documents); it is [] when the comment has none.
  • author is null when the comment was written by the respondent on an anonymous form — the respondent's identity is suppressed. Comments from managers/owners are always identified.

Signature verification

Every delivery includes two headers:

HeaderValue
TimestampUnix timestamp at the time of delivery.
SignatureHMAC-SHA256(timestamp + "." + raw_json_body, your_webhook_secret) as a hex string.

Verify both:

  1. Recompute the HMAC using your secret and compare it to the Signature header in constant time.
  2. Reject requests whose Timestamp is more than 5 minutes old to prevent replay attacks.
const crypto = require('crypto');

function verify(req, secret) {
  const ts = req.headers['timestamp'];
  const sig = req.headers['signature'];
  const body = req.rawBody; // the raw, unparsed JSON string
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${body}`)
    .digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  return true;
}

Authentication

Each webhook can also be configured with one of: Bearer token, Basic Auth, custom API key header, or arbitrary custom headers — useful when your receiver enforces its own auth on top of the signature.

Retries

Failed deliveries (any non-2xx response, or a timeout > 10 seconds) are retried with exponential backoff. Delivery logs are visible in the Outgoing Webhooks tab.