[{"data":1,"prerenderedAt":16},["ShallowReactive",2],{"doc-events":3},{"html":4,"headings":5},"\u003Cp>Things that happen in a project (a customer starts a conversation, a changelog entry goes public, a pull request\nmerges) are published as \u003Cstrong>domain events\u003C\u002Fstrong> on one bus. You can\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>receive them on your own server\u003C\u002Fstrong> as signed webhooks, each endpoint choosing the events it wants,\u003C\u002Fli>\n\u003Cli>\u003Cstrong>start a sequence\u003C\u002Fstrong> from one (see \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felrumo\u002Fhelp-bites-oss\u002Fblob\u002Fmain\u002Fdocs\u002FMARKETING.md#automations\" target=\"_blank\" rel=\"noopener\">automations\u003C\u002Fa>), and\u003C\u002Fli>\n\u003Cli>in the code, listen to them in-process with \u003Ccode>onDomainEvent\u003C\u002Fcode> (\u003Ccode>server\u002Futils\u002FdomainEvents.ts\u003C\u002Fcode>).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Webhook endpoints are part of the \u003Cem>Event webhooks\u003C\u002Fem> plan feature on the hosted edition; self-hosted has no limits.\u003C\u002Fp>\n\u003Ch2 id=\"the-event\">The event\u003Ca class=\"heading-anchor\" href=\"#the-event\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Every delivery is a \u003Ccode>POST\u003C\u002Fcode> with a JSON body:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-json\">\u003Ccode>{ \u003Cspan class=\"shj-syn-var\">\"id\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"evt_5f0c…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"type\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"changelog.entry.published\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"createdAt\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"2026-10-21T09:30:00.000Z\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"projectId\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"AbC123\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"data\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"entryId\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"…\"\u003C\u002Fspan> } }\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Ccode>data\u003C\u002Fcode> carries ids and a few small fields, never whole rows: fetch the rest with the API. Dedupe on \u003Ccode>id\u003C\u002Fcode> (a retry after a\ntimeout can arrive twice) and ignore old \u003Ccode>createdAt\u003C\u002Fcode>s.\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>\u003Ccode>type\u003C\u002Fcode>\u003C\u002Fth>\n\u003Cth>\u003Ccode>data\u003C\u002Fcode>\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>contact.created\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ contact: { id, externalId, email } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>contact.subscription_changed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ contact: { id, externalId, email }, from, to, unsubscribedTopics, topics?: { added, removed } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>conversation.created\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ conversationId, contact: { id, externalId, email }, type }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>conversation.status_changed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ conversationId, from, to, statusCategory, contact: { id } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>conversation.feedback_linked\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ conversationId, postId, linked, created }\u003C\u002Fcode> (\u003Ccode>linked\u003C\u002Fcode> is false when an operator unlinks it; \u003Ccode>created\u003C\u002Fcode> when the post was filed from the conversation)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>feedback.post.created\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ postId, number, contact?: { id, externalId } }\u003C\u002Fcode> (the author, when a person wrote it)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>feedback.post.status_changed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ postId, number, from, to }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>feedback.post.voted\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ postId, number, voterExternalId }\u003C\u002Fcode> (only when a vote is added)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>board.item.created\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ itemId, number }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>board.item.status_changed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ itemId, number, from, to }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>changelog.entry.published\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ entryId, number, releaseId, feedbackPostIds, boardItemIds, conversationIds }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>errors.filed_on_board\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ itemId, provider, issueId }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>github.commit.referenced\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ sha, repo, targetKind, targetId }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>github.pull_request.merged\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ number, repo, title, url, targets: [{ kind, id, closes }] }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email.suppressed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ email, reason: 'bounce' | 'complaint', detail }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cms.entry.published\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ entry: { id, slug, type, version, publishedAt } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>cms.entry.unpublished\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ entry: { id, slug, type } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>blog.post.published\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ post: { id, slug, title, visibility, publishedAt, url } }\u003C\u002Fcode> (\u003Ca href=\"\u002Fdocs\u002Fblog#webhooks\">BLOG.md\u003C\u002Fa>)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>blog.post.updated\u003C\u002Fcode> \u002F \u003Ccode>blog.post.unpublished\u003C\u002Fcode> \u002F \u003Ccode>blog.post.deleted\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the same\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>onboarding.completed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ flow, outcome: 'completed' | 'skipped', contact: { id, externalId, email } }\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>webhook.test\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>{ message }\u003C\u002Fcode>: only from &quot;Send a test event&quot;\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>The machine-readable list is \u003Ccode>DOMAIN_EVENTS\u003C\u002Fcode> in \u003Ccode>shared\u002FdomainEvents.ts\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"endpoints\">Endpoints\u003Ca class=\"heading-anchor\" href=\"#endpoints\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Developer → Webhook endpoints in the panel, or the API (an operator session or an \u003Ccode>ak_\u003C\u002Fcode> admin key):\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-cmnt\"># Add one. The signing secret comes back once, in this response.\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\"\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"url\": \"https:\u002F\u002Fexample.com\u002Fhooks\u002Fhelpbites\", \"events\": [\"changelog.entry.published\", \"feedback.*\"] }'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\"\u003C\u002Fspan>                     \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> PATCH \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ID\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>        \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"enabled\": false }'\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> PATCH \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ID\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>        \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"rotateSecret\": true }'\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST  \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ID\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Ftest\"\u003C\u002Fspan>   \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\u002Fdeliveries?endpoint=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ID\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> DELETE \u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$HELPBITES\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fv1\u002Fadmin\u002Fprojects\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$PROJECT\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u002Fwebhooks\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ID\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>       \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$ADMIN_KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Ccode>events\u003C\u002Fcode> is a list of filters: \u003Ccode>*\u003C\u002Fcode> (everything, the default), \u003Ccode>group.*\u003C\u002Fcode> (one group: \u003Ccode>contact\u003C\u002Fcode>, \u003Ccode>conversation\u003C\u002Fcode>, \u003Ccode>feedback\u003C\u002Fcode>,\n\u003Ccode>board\u003C\u002Fcode>, \u003Ccode>changelog\u003C\u002Fcode>, \u003Ccode>errors\u003C\u002Fcode>, \u003Ccode>github\u003C\u002Fcode>, \u003Ccode>email\u003C\u002Fcode>, \u003Ccode>cms\u003C\u002Fcode>, \u003Ccode>blog\u003C\u002Fcode>, \u003Ccode>onboarding\u003C\u002Fcode>) or an exact type. Up to 10 endpoints per project.\u003C\u002Fp>\n\u003Cp>The project's original single event webhook (Settings → Event webhook, \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felrumo\u002Fhelp-bites-oss\u002Fblob\u002Fmain\u002Fdocs\u002FMARKETING.md#event-webhook\" target=\"_blank\" rel=\"noopener\">MARKETING.md\u003C\u002Fa>) keeps\nworking and keeps receiving the events it always did (\u003Ccode>contact.subscription_changed\u003C\u002Fcode>, \u003Ccode>email.suppressed\u003C\u002Fcode>,\n\u003Ccode>cms.entry.published\u003C\u002Fcode>, \u003Ccode>cms.entry.unpublished\u003C\u002Fcode> and the four \u003Ccode>blog.post.*\u003C\u002Fcode>); add an endpoint for anything else.\u003C\u002Fp>\n\u003Ch2 id=\"verifying-and-retrying\">Verifying and retrying\u003Ca class=\"heading-anchor\" href=\"#verifying-and-retrying\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Each request carries \u003Ccode>x-helpbites-signature: sha256=&lt;hex HMAC-SHA256 of the raw body with the endpoint's secret&gt;\u003C\u002Fcode> and\n\u003Ccode>x-helpbites-event: &lt;type&gt;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-js\">\u003Ccode>\u003Cspan class=\"shj-syn-kwd\">import\u003C\u002Fspan> crypto \u003Cspan class=\"shj-syn-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'node:crypto'\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-kwd\">const\u003C\u002Fspan> expected \u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'sha256='\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">+\u003C\u002Fspan> crypto\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">createHmac\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-str\">'sha256'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> process\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>env\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">HELPBITES_WEBHOOK_SECRET\u003C\u002Fspan>)\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">update\u003C\u002Fspan>(rawBody)\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">digest\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-str\">'hex'\u003C\u002Fspan>)\n\u003Cspan class=\"shj-syn-kwd\">const\u003C\u002Fspan> got \u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">Buffer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\">from\u003C\u002Fspan>(req\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>headers[\u003Cspan class=\"shj-syn-str\">'x-helpbites-signature'\u003C\u002Fspan>] \u003Cspan class=\"shj-syn-oper\">||\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">''\u003C\u002Fspan>)\n\u003Cspan class=\"shj-syn-kwd\">if\u003C\u002Fspan> (got\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>length \u003Cspan class=\"shj-syn-oper\">!==\u003C\u002Fspan> expected\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>length \u003Cspan class=\"shj-syn-oper\">||\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">!\u003C\u002Fspan>crypto\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">timingSafeEqual\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-class\">Buffer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\">from\u003C\u002Fspan>(expected)\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> got)) \u003Cspan class=\"shj-syn-kwd\">return\u003C\u002Fspan> res\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">status\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-num\">401\u003C\u002Fspan>)\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">end\u003C\u002Fspan>()\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>import CryptoKit\n\u002F\u002F Verify on your own server (Vapor, Hummingbird…): the same HMAC over the raw body.\nfunc isValid(body: Data, header: String, secret: String) -&gt; Bool {\n  let mac = HMAC&lt;SHA256&gt;.authenticationCode(for: body, using: SymmetricKey(data: Data(secret.utf8)))\n  let expected = \u003Cspan class=\"shj-syn-str\">\"sha256=\"\u003C\u002Fspan> + mac.map { String(format: \u003Cspan class=\"shj-syn-str\">\"%02x\"\u003C\u002Fspan>, $0) }.joined()\n  return expected == header\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Anything but a 2xx is retried after 1, 5, 30, 120 and 720 minutes, then given up on. A suspended workspace's events wait.\nThe latest deliveries and their errors are in the panel and at \u003Ccode>\u002Fwebhooks\u002Fdeliveries\u003C\u002Fcode>.\u003C\u002Fp>\n",[6,10,13],{"id":7,"text":8,"level":9},"the-event","The event",2,{"id":11,"text":12,"level":9},"endpoints","Endpoints",{"id":14,"text":15,"level":9},"verifying-and-retrying","Verifying and retrying",1792677543075]