[{"data":1,"prerenderedAt":10},["ShallowReactive",2],{"public:public\u002Fcontent\u002Fdocs\u002Fwebhooks":3},{"slug":4,"title":5,"description":6,"draft":7,"updated":8,"html":9},"webhooks","Webhooks and incident tools","Signed webhooks, Microsoft Teams, PagerDuty and Opsgenie alerts for restore test results, with retries and a delivery log.",false,null,"\u003Ch1>Webhooks, Microsoft Teams, PagerDuty and Opsgenie\u003C\u002Fh1>\n\u003Cp>Add channels under \u003Cstrong>Alerts\u003C\u002Fstrong>. Every channel carries the same facts as e-mail and Slack alerts: the target name, the status and reason, timings, and the IDs of the checks that did not pass. Never check messages, observed values or anything from your database.\u003C\u002Fp>\n\u003Ch2>Signed webhook\u003C\u002Fh2>\n\u003Cp>Verestore sends a \u003Ccode>POST\u003C\u002Fcode> with a JSON body to a public \u003Ccode>https:\u002F\u002F\u003C\u002Fcode> URL of your choice for each judged run (or only for problems):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;type&quot;: &quot;run.judged&quot;,\n  &quot;id&quot;: &quot;whd_01JBX…&quot;,\n  &quot;created_at&quot;: &quot;2026-11-05T02:14:07.000Z&quot;,\n  &quot;data&quot;: {\n    &quot;run_id&quot;: &quot;run_01JBX…&quot;, &quot;org_id&quot;: &quot;org_01JBX…&quot;,\n    &quot;target&quot;: { &quot;id&quot;: &quot;tgt_01JBX…&quot;, &quot;name&quot;: &quot;orders-db&quot; },\n    &quot;status&quot;: &quot;failed&quot;, &quot;status_reason&quot;: null, &quot;judged_at&quot;: &quot;2026-11-05T02:14:07.000Z&quot;,\n    &quot;restore_seconds&quot;: 184.6, &quot;checks_not_passing&quot;: [&quot;row_counts&quot;],\n    &quot;url&quot;: &quot;https:\u002F\u002Fapp.verestore.dev\u002Fo\u002Forg_01JBX…\u002Fruns\u002Frun_01JBX…&quot;\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Headers: \u003Ccode>X-Verestore-Event\u003C\u002Fcode> (\u003Ccode>run.judged\u003C\u002Fcode> or \u003Ccode>test\u003C\u002Fcode>), \u003Ccode>X-Verestore-Delivery\u003C\u002Fcode> (the same on every retry: use it to deduplicate) and \u003Ccode>X-Verestore-Signature: t=&lt;unix seconds&gt;,v1=&lt;hex&gt;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>The signing secret (\u003Ccode>whsec_…\u003C\u002Fcode>) is shown once, when you add the channel. To verify a delivery, compute HMAC-SHA256 over \u003Ccode>&lt;t&gt;.&lt;raw body&gt;\u003C\u002Fcode> with the secret, compare it in constant time with \u003Ccode>v1\u003C\u002Fcode>, and refuse timestamps more than 5 minutes old:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">import { createHmac, timingSafeEqual } from &#39;node:crypto&#39;\n\nfunction verify(secret, rawBody, header, now = Date.now()) {\n  const m = \u002F^t=(\\d+),v1=([0-9a-f]{64})$\u002F.exec(header ?? &#39;&#39;)\n  if (!m || Math.abs(now \u002F 1000 - Number(m[1])) &gt; 300) return false\n  const want = createHmac(&#39;sha256&#39;, secret).update(`${m[1]}.${rawBody}`).digest()\n  return timingSafeEqual(want, Buffer.from(m[2], &#39;hex&#39;))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Answer with any \u003Ccode>2xx\u003C\u002Fcode> status within 10 seconds. Redirects are not followed, and the URL must resolve to a public address: this is checked when you add the channel and again on every delivery.\u003C\u002Fp>\n\u003Ch2>Microsoft Teams\u003C\u002Fh2>\n\u003Cp>Create a \u003Cstrong>Workflows\u003C\u002Fstrong> flow &quot;Post to a channel when a webhook request is received&quot; (or an incoming webhook connector) and paste its URL. Verestore posts an Adaptive Card with the status, timings, the checks that did not pass and an \u003Cstrong>Open run\u003C\u002Fstrong> button. Only Microsoft webhook hosts are accepted.\u003C\u002Fp>\n\u003Ch2>PagerDuty\u003C\u002Fh2>\n\u003Cp>Add an \u003Cstrong>Events API v2\u003C\u002Fstrong> integration to a service and paste its integration key; choose the EU region if your account is hosted there. A warned, failed or inconclusive run triggers an incident with deduplication key \u003Ccode>verestore:&lt;target id&gt;\u003C\u002Fcode> (so repeated failures of one target update one incident), and the next passing run of that target resolves it.\u003C\u002Fp>\n\u003Ch2>Opsgenie\u003C\u002Fh2>\n\u003Cp>Create an \u003Cstrong>API\u003C\u002Fstrong> integration and paste its key; choose the EU region if your account is hosted there. A problem run creates an alert with alias \u003Ccode>verestore:&lt;target id&gt;\u003C\u002Fcode> (P2 for failed, P3 otherwise); the next passing run of that target closes it.\u003C\u002Fp>\n\u003Ch2>Retries and the delivery log\u003C\u002Fh2>\n\u003Cp>Webhook, Teams, PagerDuty and Opsgenie deliveries are retried on network errors, timeouts, \u003Ccode>408\u003C\u002Fcode>, \u003Ccode>429\u003C\u002Fcode> and \u003Ccode>5xx\u003C\u002Fcode>: after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours, for at most 24 hours from the first attempt. Other \u003Ccode>4xx\u003C\u002Fcode> answers are final. The \u003Cstrong>Delivery log\u003C\u002Fstrong> on the Alerts page shows each delivery&#39;s status, HTTP status, number of attempts and next attempt. \u003Cstrong>Send test\u003C\u002Fstrong> delivers a test message once, without retries.\u003C\u002Fp>\n",1794290783592]