{
  "headers": {
    "signature": "Mediflo-Signature",
    "eventId": "Mediflo-Event-Id",
    "eventType": "Mediflo-Event-Type",
    "test": "Mediflo-Event-Test"
  },
  "signatureToleranceSeconds": 300,
  "deliveryContract": {
    "httpTimeoutMs": 10000,
    "retryLadderSeconds": [
      60,
      300,
      1800,
      7200,
      28800,
      86400
    ],
    "autoDisableConsecutiveFailures": 20,
    "autoDisableMinSilenceHours": 72
  },
  "deliveryRules": [
    "AT-LEAST-ONCE. The same event can arrive more than once (a retry after a timeout your server actually processed, a redelivery, a rotation window). Deduplicate on the envelope `id` — the `evt_…` value, which is stable across every attempt of the same event — and make your handler idempotent. `Mediflo-Event-Id` carries the same value in a header.",
    "NO ORDERING GUARANTEE. Deliveries are independent jobs; a `task.updated` can land before the `task.created` it follows. Order by the envelope `occurredAt`, never by arrival, and treat a payload with a `revision` field as authoritative only when its revision is higher than what you already stored.",
    "ANSWER 2xx WITHIN 10 SECONDS. Anything else — a timeout, a 4xx, a 5xx — is a failed attempt. Acknowledge first and do the work afterwards; a receiver that processes inline is the single most common cause of a retry storm against a server that is working fine.",
    "REDIRECTS ARE FAILURES. 3xx is recorded as a failed attempt and is never followed — register the final https URL, not a redirector.",
    "RETRY LADDER: 1m → 5m → 30m → 2h → 8h → 24h after each successive failure, i.e. 7 attempts over roughly 35 hours. After the last one the delivery is `EXHAUSTED` and we stop on our own; replay it with redeliver_webhook once your receiver is fixed.",
    "AUTO-DISABLE: an endpoint with 20 consecutive failures AND no success for 72 h is switched to `AUTO_DISABLED` and stops receiving anything. Both conditions are required, so a bad deploy window alone will not trip it. Re-enable with update_webhook_endpoint `status: \"ENABLED\"`, which also clears the failure streak.",
    "PARSE LENIENTLY. Fields are ADDED to payloads without a version bump — reject unknown keys and a future release breaks your receiver. Read the keys you need and ignore the rest; `apiVersion` on the envelope changes only for a breaking change.",
    "TEST TRAFFIC IS FLAGGED. A test send carries `Mediflo-Event-Test`; the header is ABSENT — not `false` — on a real event, so the correct check is presence (`if (headers[…])`), never a comparison to a string. Branch on it and drop test deliveries before your production processing."
  ],
  "secretContract": {
    "shownOnceBy": [
      "create_webhook_endpoint",
      "rotate_webhook_secret"
    ],
    "rotationGraceHours": 24,
    "rules": [
      "THE SECRET IS RETURNED EXACTLY ONCE, by the call that mints it. It is stored AES-GCM-encrypted and there is no endpoint that reads it back. Hand it to the human operating this integration, or write it into the receiver’s secret store, in the same turn you receive it — losing it means rotating, which starts a new grace window.",
      "Rotation keeps the OUTGOING secret signing for 24 h alongside the new one, so a receiver can be redeployed without dropping deliveries. `previousSecretExpiresAt` on the endpoint says when the old one stops.",
      "Never log the secret, never put it in a URL, and never echo it back to the user in a transcript that gets stored."
    ]
  },
  "snippets": {
    "ts": "import { createHmac, timingSafeEqual } from 'node:crypto'\n\n/**\n * Verify a Mediflo webhook signature.\n *\n * Header shape: `t=<unix-seconds>,v1=<hex>[,v1=<hex>]`\n * where `v1 = HMAC_SHA256(secret, \"<t>.<rawBody>\")`.\n *\n * `rawBody` MUST be the exact bytes we sent. Re-serializing parsed JSON does\n * not reproduce the digest — in Express, use\n * `express.json({ verify: (req, _res, buf) => { req.rawBody = buf } })`.\n *\n * Pass BOTH secrets during a rotation window (or just the current one; the\n * header carries a `v1=` entry per active secret and any match is valid).\n */\nexport function verifyMedifloSignature(\n  header: string | undefined,\n  rawBody: string | Buffer,\n  secrets: string[],\n  toleranceSeconds = 300\n): boolean {\n  if (!header || secrets.length === 0) return false\n\n  let timestamp: number | null = null\n  const presented: string[] = []\n\n  for (const part of header.split(',')) {\n    const trimmed = part.trim()\n    const eq = trimmed.indexOf('=')\n    if (eq <= 0) continue\n    const key = trimmed.slice(0, eq)\n    const value = trimmed.slice(eq + 1)\n    if (key === 't') {\n      if (!/^\\d+$/.test(value)) return false\n      timestamp = Number(value)\n    } else if (key === 'v1') {\n      // More than one entry appears during a 24 h secret rotation.\n      presented.push(value)\n    }\n  }\n\n  if (timestamp === null || presented.length === 0) return false\n\n  // Replay protection: the timestamp is INSIDE the signed string, so an\n  // attacker cannot move it forward without invalidating every digest.\n  const now = Math.floor(Date.now() / 1000)\n  if (Math.abs(now - timestamp) > toleranceSeconds) return false\n\n  for (const secret of secrets) {\n    const expected = createHmac('sha256', secret)\n      .update(`${timestamp}.${rawBody}`)\n      .digest('hex')\n\n    for (const candidate of presented) {\n      // Validate the hex BEFORE comparing. Buffer.from('zz', 'hex') truncates\n      // silently, and timingSafeEqual throws RangeError on a length mismatch —\n      // so without these two guards a malformed header crashes the handler\n      // instead of returning false.\n      const normalized = candidate.trim().toLowerCase()\n      if (!/^[0-9a-f]+$/.test(normalized)) continue\n      if (normalized.length !== expected.length) continue\n\n      if (\n        timingSafeEqual(\n          Buffer.from(normalized, 'hex'),\n          Buffer.from(expected, 'hex')\n        )\n      ) {\n        return true\n      }\n    }\n  }\n\n  return false\n}",
    "php": "<?php\n\n/**\n * Verify a Mediflo webhook signature.\n *\n * Header shape: t=<unix-seconds>,v1=<hex>[,v1=<hex>]\n * where v1 = HMAC_SHA256(secret, \"<t>.<rawBody>\").\n *\n * $rawBody MUST be the exact bytes we sent — read them with\n * file_get_contents('php://input'), never json_encode(json_decode(...)).\n *\n * Pass both secrets during a 24 h rotation window; any matching v1= is valid.\n *\n * @param string[] $secrets\n */\nfunction verify_mediflo_signature(\n    ?string $header,\n    string $rawBody,\n    array $secrets,\n    int $toleranceSeconds = 300\n): bool {\n    if ($header === null || $header === '' || count($secrets) === 0) {\n        return false;\n    }\n\n    $timestamp = null;\n    $presented = [];\n\n    foreach (explode(',', $header) as $part) {\n        $part = trim($part);\n        $eq = strpos($part, '=');\n        if ($eq === false || $eq === 0) {\n            continue;\n        }\n        $key = substr($part, 0, $eq);\n        $value = substr($part, $eq + 1);\n\n        if ($key === 't') {\n            if (preg_match('/^\\d+$/', $value) !== 1) {\n                return false;\n            }\n            $timestamp = (int) $value;\n        } elseif ($key === 'v1') {\n            // More than one entry appears during a 24 h secret rotation.\n            $presented[] = $value;\n        }\n    }\n\n    if ($timestamp === null || count($presented) === 0) {\n        return false;\n    }\n\n    // Replay protection: the timestamp is inside the signed string.\n    if (abs(time() - $timestamp) > $toleranceSeconds) {\n        return false;\n    }\n\n    foreach ($secrets as $secret) {\n        $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n\n        foreach ($presented as $candidate) {\n            // Validate the hex first. hash_equals() does not throw, but it also\n            // does not hide a length difference, and comparing raw bytes from a\n            // malformed hex string would compare garbage.\n            $normalized = strtolower(trim($candidate));\n            if (ctype_xdigit($normalized) !== true) {\n                continue;\n            }\n            if (strlen($normalized) !== strlen($expected)) {\n                continue;\n            }\n            // hash_equals is the constant-time comparison; === is not.\n            if (hash_equals($expected, $normalized)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}"
  },
  "events": [
    {
      "type": "ping",
      "family": "ping",
      "description": "Test event sent by the endpoint \"test\" action. Carries no call or task data.",
      "descriptionPl": "Zdarzenie testowe wysyłane przyciskiem „Wyślij testowy ping”. Nie zawiera danych rozmów ani zadań.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_ping",
        "type": "ping",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "message": "ping"
        }
      }
    },
    {
      "type": "bot_call.completed",
      "family": "bot_call",
      "description": "A bot call reached a terminal state (completed, no_answer, voicemail, failed or stuck).",
      "descriptionPl": "Rozmowa bota dobiegła końca (odebrana, brak odpowiedzi, poczta głosowa, błąd lub zawieszenie).",
      "phiBlocks": [
        "phoneNumbers",
        "outcome",
        "transcript",
        "contact"
      ],
      "sample": {
        "id": "evt_sample_bot_call_completed",
        "type": "bot_call.completed",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "dispatchId": "00000000-0000-0000-0000-000000000001",
          "botId": "00000000-0000-0000-0000-000000000002",
          "attemptId": "00000000-0000-0000-0000-000000000003",
          "status": "completed",
          "endReason": "bot_hangup",
          "durationSec": 74,
          "revision": 1767258000000,
          "hasTranscript": true,
          "hasRecording": true,
          "links": {
            "self": "/api/external/v1/bot-sessions/00000000-0000-0000-0000-000000000001",
            "transcript": "/api/external/v1/bot-sessions/00000000-0000-0000-0000-000000000001/transcript",
            "recording": "/api/external/v1/bot-sessions/00000000-0000-0000-0000-000000000001/recording"
          },
          "phoneNumbers": {
            "to": "+48123456789"
          },
          "outcome": {
            "confirmed": true,
            "note": "PRZYKŁAD / SAMPLE — Jan Testowy potwierdził termin."
          },
          "contact": {
            "contactId": "00000000-0000-0000-0000-000000000008"
          }
        }
      }
    },
    {
      "type": "call.completed",
      "family": "call",
      "description": "A human call reached a terminal state.",
      "descriptionPl": "Rozmowa obsłużona przez człowieka dobiegła końca.",
      "phiBlocks": [
        "phoneNumbers",
        "outcome",
        "transcript",
        "contact"
      ],
      "sample": {
        "id": "evt_sample_call_completed",
        "type": "call.completed",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "callId": "00000000-0000-0000-0000-000000000004",
          "direction": "INBOUND",
          "status": "COMPLETED",
          "startedAt": "2026-01-01T09:00:00.000Z",
          "answeredAt": "2026-01-01T09:00:06.000Z",
          "endedAt": "2026-01-01T09:03:11.000Z",
          "durationSec": 185,
          "totalDurationSec": 191,
          "queueId": null,
          "voicemailDetected": false,
          "agent": {
            "userId": "00000000-0000-0000-0000-000000000009",
            "email": "jan.testowy@example.com"
          },
          "revision": 1767258000000,
          "hasTranscript": true,
          "hasRecording": true,
          "links": {
            "self": "/api/external/v1/calls/00000000-0000-0000-0000-000000000004"
          },
          "phoneNumbers": {
            "from": "+48123456789",
            "to": "+48987654321"
          },
          "contact": {
            "contactId": "00000000-0000-0000-0000-000000000008"
          }
        }
      }
    },
    {
      "type": "call.updated",
      "family": "call",
      "description": "A previously reported call was corrected — e.g. the voicemail reclassifier changed a NO_ANSWER verdict.",
      "descriptionPl": "Korekta wcześniej zgłoszonej rozmowy — np. reklasyfikacja poczty głosowej zmieniła werdykt.",
      "phiBlocks": [
        "phoneNumbers",
        "outcome",
        "transcript",
        "contact"
      ],
      "sample": {
        "id": "evt_sample_call_updated",
        "type": "call.updated",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "callId": "00000000-0000-0000-0000-000000000004",
          "changed": [
            "status",
            "voicemailDetected"
          ],
          "status": "NO_ANSWER",
          "voicemailDetected": true,
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/calls/00000000-0000-0000-0000-000000000004"
          }
        }
      }
    },
    {
      "type": "call.transcript.ready",
      "family": "call",
      "description": "Transcription finished for a human call.",
      "descriptionPl": "Transkrypcja rozmowy jest gotowa.",
      "phiBlocks": [
        "phoneNumbers",
        "outcome",
        "transcript",
        "contact"
      ],
      "sample": {
        "id": "evt_sample_call_transcript_ready",
        "type": "call.transcript.ready",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "callId": "00000000-0000-0000-0000-000000000004",
          "transcriptionId": "00000000-0000-0000-0000-000000000005",
          "requestedLanguage": "pl",
          "detectedLanguage": "pl",
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/calls/00000000-0000-0000-0000-000000000004",
            "transcript": "/api/external/v1/calls/00000000-0000-0000-0000-000000000004/transcript"
          }
        }
      }
    },
    {
      "type": "call.analysis.completed",
      "family": "call",
      "description": "Post-call analysis finished for a call. `insights.profile` names the taxonomy the insight values come from (medical, commercial, b2b or b2c), or is null when it is not known — for example for calls analysed before profiles existed.",
      "descriptionPl": "Analiza porozmowna została zakończona. Pole `insights.profile` wskazuje taksonomię, z której pochodzą wartości analizy.",
      "phiBlocks": [
        "phoneNumbers",
        "outcome",
        "transcript",
        "contact"
      ],
      "sample": {
        "id": "evt_sample_call_analysis_completed",
        "type": "call.analysis.completed",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "callId": "00000000-0000-0000-0000-000000000004",
          "runId": "00000000-0000-0000-0000-000000000006",
          "runStatus": "COMPLETED",
          "insights": {
            "primaryIntent": "order_status",
            "category": "order_status",
            "payer": null,
            "outcome": "handled",
            "booking": "not_applicable",
            "callerSatisfaction": "satisfied",
            "unmetDemandPresent": false,
            "confidence": 0.86,
            "profile": "commercial"
          },
          "summary": "PRZYKŁAD / SAMPLE — Jan Testowy zapytał o status zamówienia; dostawa jutro.",
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/calls/00000000-0000-0000-0000-000000000004"
          }
        }
      }
    },
    {
      "type": "task.created",
      "family": "task",
      "description": "A task was created, including AI-suggested tasks (the AI rationale is never included).",
      "descriptionPl": "Powstało zadanie, także sugerowane przez AI (uzasadnienie AI nigdy nie trafia do webhooka).",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_task_created",
        "type": "task.created",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "taskId": "00000000-0000-0000-0000-000000000007",
          "title": "PRZYKŁAD / SAMPLE — oddzwoń do: Jan Testowy",
          "description": "Przykładowe zadanie z testowego webhooka. Nie jest realne.",
          "status": "TODO",
          "priority": "MEDIUM",
          "origin": "SYSTEM",
          "systemSource": "call_analysis",
          "suggestionConfidence": 0.72,
          "suggestionOutcome": null,
          "dueDate": "2026-01-02T09:00:00.000Z",
          "createdAt": "2026-01-01T09:00:00.000Z",
          "assignedTo": {
            "userId": "00000000-0000-0000-0000-000000000009",
            "email": "jan.testowy@example.com"
          },
          "suggestedAssignee": null,
          "linked": {
            "telephonyCallId": "00000000-0000-0000-0000-000000000004",
            "dispatchId": null,
            "contactId": "00000000-0000-0000-0000-000000000008",
            "patientId": null,
            "companyId": null
          },
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/tasks/00000000-0000-0000-0000-000000000007"
          }
        }
      }
    },
    {
      "type": "task.updated",
      "family": "task",
      "description": "A task changed — status, assignee, priority or due date.",
      "descriptionPl": "Zmiana w zadaniu — status, osoba, priorytet lub termin.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_task_updated",
        "type": "task.updated",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "taskId": "00000000-0000-0000-0000-000000000007",
          "title": "PRZYKŁAD / SAMPLE — oddzwoń do: Jan Testowy",
          "description": "Przykładowe zadanie z testowego webhooka. Nie jest realne.",
          "status": "IN_PROGRESS",
          "priority": "HIGH",
          "origin": "SYSTEM",
          "systemSource": "call_analysis",
          "suggestionConfidence": 0.72,
          "suggestionOutcome": "ACCEPTED",
          "dueDate": "2026-01-02T09:00:00.000Z",
          "createdAt": "2026-01-01T09:00:00.000Z",
          "assignedTo": {
            "userId": "00000000-0000-0000-0000-000000000009",
            "email": "jan.testowy@example.com"
          },
          "suggestedAssignee": null,
          "linked": {
            "telephonyCallId": "00000000-0000-0000-0000-000000000004",
            "dispatchId": null,
            "contactId": "00000000-0000-0000-0000-000000000008",
            "patientId": null,
            "companyId": null
          },
          "revision": 1767258000000,
          "changed": [
            "status",
            "priority"
          ],
          "links": {
            "self": "/api/external/v1/tasks/00000000-0000-0000-0000-000000000007"
          }
        }
      }
    },
    {
      "type": "task.deleted",
      "family": "task",
      "description": "A task was deleted.",
      "descriptionPl": "Zadanie zostało usunięte.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_task_deleted",
        "type": "task.deleted",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "taskId": "00000000-0000-0000-0000-000000000007",
          "deletedAt": "2026-01-01T09:00:00.000Z",
          "links": {
            "self": "/api/external/v1/tasks/00000000-0000-0000-0000-000000000007"
          }
        }
      }
    },
    {
      "type": "task.suggestion.resolved",
      "family": "task",
      "description": "An AI-suggested task was accepted, rejected or expired.",
      "descriptionPl": "Sugestia zadania została przyjęta, odrzucona albo wygasła.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_task_suggestion_resolved",
        "type": "task.suggestion.resolved",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "taskId": "00000000-0000-0000-0000-000000000007",
          "resolution": "accepted",
          "suggestionOutcome": "ACCEPTED",
          "resolvedAt": "2026-01-01T09:00:00.000Z",
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/tasks/00000000-0000-0000-0000-000000000007"
          }
        }
      }
    },
    {
      "type": "case.created",
      "family": "case",
      "description": "A customer case was created. Offered only to organisations with customer cases enabled.",
      "descriptionPl": "Powstała sprawa klienta. Dostępne tylko dla organizacji z włączonymi sprawami.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_case_created",
        "type": "case.created",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "caseId": "00000000-0000-0000-0000-000000000011",
          "number": 1042,
          "title": "PRZYKŁAD / SAMPLE — zgłoszenie od: Jan Testowy",
          "description": "Przykładowa sprawa z testowego webhooka. Nie jest realna.",
          "status": "NEW",
          "priority": "MEDIUM",
          "categoryKey": "service_request",
          "source": "CALL",
          "topicId": "00000000-0000-0000-0000-000000000013",
          "contactId": "00000000-0000-0000-0000-000000000008",
          "companyId": "00000000-0000-0000-0000-000000000012",
          "assignedTo": {
            "userId": "00000000-0000-0000-0000-000000000009",
            "email": "jan.testowy@example.com"
          },
          "createdAt": "2026-01-01T09:00:00.000Z",
          "statusChangedAt": "2026-01-01T09:00:00.000Z",
          "dueAt": "2026-01-03T09:00:00.000Z",
          "firstResponseAt": null,
          "resolvedAt": null,
          "closedAt": null,
          "linkSummary": {
            "total": 1,
            "items": [
              {
                "resourceType": "TELEPHONY_CALL",
                "resourceId": "00000000-0000-0000-0000-000000000004"
              }
            ]
          },
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/cases/00000000-0000-0000-0000-000000000011"
          }
        }
      }
    },
    {
      "type": "case.updated",
      "family": "case",
      "description": "A customer case changed — status, assignee, priority, classification, due date, linked records or a new internal note; `changed` lists which (note content is never included).",
      "descriptionPl": "Zmiana w sprawie — status, osoba, priorytet, kategoria, termin, powiązane rekordy albo nowa notatka; pole `changed` wskazuje, co się zmieniło (treść notatek nigdy nie trafia do webhooka).",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_case_updated",
        "type": "case.updated",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "caseId": "00000000-0000-0000-0000-000000000011",
          "number": 1042,
          "title": "PRZYKŁAD / SAMPLE — zgłoszenie od: Jan Testowy",
          "description": "Przykładowa sprawa z testowego webhooka. Nie jest realna.",
          "status": "IN_PROGRESS",
          "priority": "HIGH",
          "categoryKey": "service_request",
          "source": "CALL",
          "topicId": "00000000-0000-0000-0000-000000000013",
          "contactId": "00000000-0000-0000-0000-000000000008",
          "companyId": "00000000-0000-0000-0000-000000000012",
          "assignedTo": {
            "userId": "00000000-0000-0000-0000-000000000009",
            "email": "jan.testowy@example.com"
          },
          "createdAt": "2026-01-01T09:00:00.000Z",
          "statusChangedAt": "2026-01-01T10:00:00.000Z",
          "dueAt": "2026-01-03T09:00:00.000Z",
          "firstResponseAt": null,
          "resolvedAt": null,
          "closedAt": null,
          "linkSummary": {
            "total": 1,
            "items": [
              {
                "resourceType": "TELEPHONY_CALL",
                "resourceId": "00000000-0000-0000-0000-000000000004"
              }
            ]
          },
          "revision": 1767258000000,
          "links": {
            "self": "/api/external/v1/cases/00000000-0000-0000-0000-000000000011"
          },
          "changed": [
            "status",
            "priority"
          ]
        }
      }
    },
    {
      "type": "case.deleted",
      "family": "case",
      "description": "A customer case was deleted.",
      "descriptionPl": "Sprawa została usunięta.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_case_deleted",
        "type": "case.deleted",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "caseId": "00000000-0000-0000-0000-000000000011",
          "number": 1042,
          "deletedAt": "2026-01-01T09:00:00.000Z",
          "links": {
            "self": "/api/external/v1/cases/00000000-0000-0000-0000-000000000011"
          }
        }
      }
    },
    {
      "type": "webhook_endpoint.auto_disabled",
      "family": "webhook_endpoint",
      "description": "An endpoint was auto-disabled after a sustained delivery failure window.",
      "descriptionPl": "Webhook został wyłączony automatycznie po serii nieudanych dostarczeń.",
      "phiBlocks": [],
      "sample": {
        "id": "evt_sample_webhook_endpoint_auto_disabled",
        "type": "webhook_endpoint.auto_disabled",
        "apiVersion": "v1",
        "occurredAt": "2026-01-01T09:00:00.000Z",
        "data": {
          "endpointId": "00000000-0000-0000-0000-000000000010",
          "endpointOrigin": "https://example.com",
          "consecutiveFailures": 20,
          "lastFailureAt": "2026-01-01T09:00:00.000Z"
        }
      }
    }
  ]
}
