Rundunrundun

Webhook events

All events share the same outer envelope. The data object is event-specific.

Common envelope

{
  "event_id":         "evt-550e8400-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event":            "step.completed",
  "run_id":           "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id":      "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v":       3,
  "ts":               "2026-05-22T08:33:12Z",
  "identity": {
    "mode": "member",
    "user_id": "u-abc12300-e29b-41d4-a716-446655440000"
  },
  "data": { }
}
Field Description
event_id Stable UUID per event — identical across all retry attempts. Use as idempotency key.
delivery_attempt Which attempt this is (1–5).
event One of the five event type strings.
run_id The run this event belongs to.
template_id The template this run is based on.
template_v Template version snapshotted at run creation.
ts ISO 8601 timestamp of when the event fired.
identity How the executor identified themselves — anonymous, external, or member.
data Event-specific payload — see each event below.

step.completed

Fires after each step is answered (all required photos for that step have been captured).

Photos are embedded as base64 JPEG thumbnails — approximately 25KB each, so a step with 5 photos adds about 125KB to the payload. This keeps the event self-contained for live progress tracking.

{
  "event_id": "evt-550e8400-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "step.completed",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-22T08:33:12Z",
  "identity": { "mode": "member", "user_id": "u-abc12300-e29b-41d4-a716-446655440000" },
  "data": {
    "step_id": "s-dm01",
    "step_label": "Any pre-existing damage?",
    "step_type": "boolean",
    "answer": true,
    "photos": [],
    "geo": null,
    "progress": { "total": 6, "answered": 3, "skipped": 0 }
  }
}

With photos (step has photo overlay)

{
  "event_id": "evt-7a3f2100-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "step.completed",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-22T08:35:00Z",
  "identity": { "mode": "member", "user_id": "u-abc12300-e29b-41d4-a716-446655440000" },
  "data": {
    "step_id": "s-dm02",
    "step_label": "Damage type",
    "step_type": "multi_choice",
    "answer": ["o-dm1", "o-dm3"],
    "photos": [
      {
        "photo_index": 0,
        "preview": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...",
        "width": 320,
        "height": 240
      }
    ],
    "geo": { "lat": 51.507, "lng": -0.127, "accuracy_m": 5 },
    "progress": { "total": 6, "answered": 4, "skipped": 0 }
  }
}

Answer shapes by step type

Step type answer value
boolean true or false
choice "o-fu2" (option ID)
multi_choice ["o-dm1", "o-dm3"] (array of option IDs)
text "Scratch on rear left door"
number 42150
rating 4
photo null (the answer is the photos array)
signature "https://media.rundun.app/runs/r-.../s-sg01/sig.png"
barcode "VIN1234567890"
datetime "2026-05-10" or "2026-05-10T14:00:00Z"
instruction null (acknowledged)

step.skipped

Fires when a non-required step is explicitly skipped by the executor. Only fires if settings.allow_skip is true on the template.

{
  "event_id": "evt-7f3a1200-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "step.skipped",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-22T08:34:01Z",
  "identity": { "mode": "member", "user_id": "u-abc12300-e29b-41d4-a716-446655440000" },
  "data": {
    "step_id": "s-nt01",
    "step_label": "Additional notes",
    "step_type": "text",
    "progress": { "total": 6, "answered": 3, "skipped": 1 }
  }
}

run.completed

Fires when all required steps are answered and the executor submits the run.

Photos in run.completed are full-resolution URLs — not base64 thumbnails. The background upload from the device to Rundun's cloud storage completes between step answers, so by the time the run is submitted the URLs are available.

{
  "event_id": "evt-9c2b0400-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "run.completed",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-22T08:41:05Z",
  "identity": { "mode": "member", "user_id": "u-abc12300-e29b-41d4-a716-446655440000" },
  "data": {
    "duration_seconds": 612,
    "progress": { "total": 6, "answered": 5, "skipped": 1 },
    "answers": {
      "s-od01": {
        "answer": 42150,
        "photos": [],
        "geo": null
      },
      "s-fu01": {
        "answer": "o-fu2",
        "photos": [],
        "geo": null
      },
      "s-dm01": {
        "answer": true,
        "photos": [],
        "geo": null
      },
      "s-dm02": {
        "answer": ["o-dm1", "o-dm3"],
        "photos": [
          {
            "photo_index": 0,
            "url": "https://media.rundun.app/runs/r-550e8400-e29b-41d4-a716-446655440000/s-dm02/photo_0.jpg"
          }
        ],
        "geo": { "lat": 51.507, "lng": -0.127, "accuracy_m": 5 }
      },
      "s-dm03": {
        "answer": "Scratch on rear left door, crack on front bumper",
        "photos": [],
        "geo": null
      },
      "s-sg01": {
        "answer": "https://media.rundun.app/runs/r-550e8400-e29b-41d4-a716-446655440000/s-sg01/sig.png",
        "photos": [],
        "geo": null
      }
    }
  }
}
data field Description
duration_seconds Seconds from run creation to submission
progress Final step counts
answers Full answer map keyed by step ID — same answer shapes as step.completed

run.cancelled

Fires when the executor explicitly exits the run before completing it.

{
  "event_id": "evt-4e8d0100-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "run.cancelled",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-22T08:37:22Z",
  "identity": { "mode": "member", "user_id": "u-abc12300-e29b-41d4-a716-446655440000" },
  "data": {
    "reason": "user_exit",
    "progress": { "total": 6, "answered": 3, "skipped": 0 },
    "last_step_id": "s-dm02",
    "answers_so_far": {
      "s-od01": { "answer": 42150, "photos": [], "geo": null },
      "s-fu01": { "answer": "o-fu2", "photos": [], "geo": null },
      "s-dm01": { "answer": true, "photos": [], "geo": null }
    }
  }
}
data field Description
reason Always "user_exit" — executor tapped the exit/cancel control
progress Step counts at the point of cancellation
last_step_id The step that was active when the executor cancelled
answers_so_far Partial answers collected before cancellation

run.expired

Fires when the run link expires before the executor completes it. If expires_in_hours was not set on the run, this event never fires.

Expiry is checked on access — the event fires when the executor (or the system) attempts to open an expired run, not at the exact expiry time.

{
  "event_id": "evt-2b5f0900-e29b-41d4-a716-446655440000",
  "delivery_attempt": 1,
  "event": "run.expired",
  "run_id": "r-550e8400-e29b-41d4-a716-446655440000",
  "template_id": "t-a3f9bc00-e29b-41d4-a716-446655440000",
  "template_v": 3,
  "ts": "2026-05-23T08:31:00Z",
  "identity": { "mode": "anonymous" },
  "data": {
    "expired_at": "2026-05-23T08:31:00Z",
    "progress": { "total": 6, "answered": 0, "skipped": 0 }
  }
}

The identity is anonymous when the run expired without being opened. If the executor opened the run and partially completed it before expiry, identity reflects how they identified.