{"id":186,"date":"2026-09-22T08:40:33","date_gmt":"2026-09-22T02:40:33","guid":{"rendered":"https:\/\/nirohelp.com\/docs\/?post_type=nirohelp_doc&#038;p=186"},"modified":"2026-09-22T09:08:43","modified_gmt":"2026-09-22T03:08:43","slug":"webhooks","status":"publish","type":"nirohelp_doc","link":"https:\/\/nirohelp.com\/docs\/integrations\/webhooks\/","title":{"rendered":"Webhooks"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">NiroHelp fires a canonical event for every meaningful thing that happens to a ticket \u2014 created, replied to, assigned, overdue, resolved, and more. A webhook sends that event as a signed HTTPS request to a URL of your own, so you can wire NiroHelp into Make, n8n, a serverless function, or anything else that takes a POST.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Where the screen is<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>NiroHelp \u2192 Integrations \u2192 Webhooks.<\/strong> Like the rest of Integrations, it needs the <code>manage_options<\/code> capability, so only administrators see it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding an endpoint<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to <strong>NiroHelp \u2192 Integrations \u2192 Webhooks<\/strong>.<\/li>\n\n\n\n<li>Click <strong>Add endpoint<\/strong>.<\/li>\n\n\n\n<li>Fill in:\n<ul class=\"wp-block-list\">\n<li><strong>Endpoint URL<\/strong> \u2014 where the request goes. See <em>URL rules<\/em> below.<\/li>\n\n\n\n<li><strong>Description<\/strong> \u2014 a note for yourself; shown in the endpoint list.<\/li>\n\n\n\n<li><strong>Include message bodies<\/strong> \u2014 off by default. On sends the full text of replies and notes; off sends a short excerpt only.<\/li>\n\n\n\n<li><strong>Events<\/strong> \u2014 which events this endpoint receives. Pick <strong>All public events<\/strong>, specific events, or both.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click <strong>Add endpoint<\/strong>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The endpoint is created active, and its signing secret is shown once \u2014 see below.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">URL rules<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Must be <code>https:\/\/<\/code> \u2014 plain HTTP is refused, unless the site has <code>WP_DEBUG<\/code> on.<\/li>\n\n\n\n<li>Can&#8217;t resolve to this server or a private network address. NiroHelp checks this the same way WordPress does for any outgoing request, so a URL that can&#8217;t be reached safely is rejected before it&#8217;s ever saved.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Choosing events<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>All public events<\/strong> is a subscription, not a shortcut \u2014 it&#8217;s stored as <code>*<\/code> and keeps covering new events added in later releases, which ticking every current checkbox individually would not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It deliberately stops at <em>public<\/em> events. A private event carries agent-only content \u2014 today that&#8217;s only <strong>Private note added<\/strong> \u2014 and it is never included in &#8220;All public events.&#8221; If you want it, choose it by name as well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A handful of events are listed as <strong>Not emitted yet<\/strong>. Their names are published so you can build against them now, but nothing fires them yet \u2014 see the table below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Events you can subscribe to<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Event<\/th><th>Fires when<\/th><\/tr><\/thead><tbody><tr><td><code>ticket.created<\/code><\/td><td>A ticket was submitted, created in wp-admin, or imported.<\/td><\/tr><tr><td><code>ticket.updated<\/code><\/td><td>Urgency, product, reason, order or a custom field changed. At most one per ticket per request.<\/td><\/tr><tr><td><code>ticket.replied<\/code><\/td><td>The customer, an agent or the AI replied.<\/td><\/tr><tr><td><code>ticket.customer_replied<\/code><\/td><td>The customer specifically replied. The same reply also fires <code>ticket.replied<\/code>.<\/td><\/tr><tr><td><code>ticket.note_added<\/code><\/td><td><em>Private.<\/em> An agent-only note was added.<\/td><\/tr><tr><td><code>ticket.assigned<\/code><\/td><td>A ticket was assigned, reassigned or unassigned.<\/td><\/tr><tr><td><code>ticket.status_changed<\/code><\/td><td>A ticket moved from one status to another.<\/td><\/tr><tr><td><code>ticket.resolved<\/code><\/td><td>A ticket was marked resolved. The same change also fires <code>ticket.status_changed<\/code>.<\/td><\/tr><tr><td><code>ticket.overdue<\/code><\/td><td>A ticket has waited on an agent longer than <strong>Overdue after<\/strong>, under <a href=\"..\/Setup%20&amp;%20Configuration\/02-settings-reference.md\">Settings \u2192 Tickets \u2192 Behaviour<\/a>.<\/td><\/tr><tr><td><code>ticket.sentiment_negative<\/code><\/td><td>A ticket&#8217;s sentiment turned negative. Fired on the change, not on every re-read.<\/td><\/tr><tr><td><code>customer.created<\/code><\/td><td>A customer account was created.<\/td><\/tr><tr><td><code>ai.answered<\/code><\/td><td>The AI auto responder replied to a ticket.<\/td><\/tr><tr><td><code>ai.escalated<\/code><\/td><td>The AI held an answer back, or couldn&#8217;t answer \u2014 a person is needed.<\/td><\/tr><tr><td><code>ticket.reopened<\/code><\/td><td><em>Not emitted yet.<\/em> A customer replied to a finished ticket and it reopened.<\/td><\/tr><tr><td><code>sla.breached<\/code><\/td><td><em>Not emitted yet.<\/em> A ticket missed an SLA target.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">The signing secret<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every endpoint gets its own secret, prefixed <code>whsec_<\/code>. <strong>It&#8217;s shown exactly once<\/strong> \u2014 right after you add the endpoint, or right after you rotate it \u2014 in a <strong>Copy your signing secret<\/strong> dialog. Close that dialog and it&#8217;s gone; the endpoint list only ever shows a hint of it afterwards.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every delivery carries an <code>X-NiroHelp-Signature<\/code> header:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>X-NiroHelp-Signature: t=&lt;unix timestamp&gt;,v1=&lt;hex&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>v1<\/code> is an HMAC-SHA256 of <code>\"{t}.{body}\"<\/code>, computed with your endpoint&#8217;s secret. To verify a request came from your site, recompute that HMAC on the raw request body and compare it to <code>v1<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function verify_signature( $header, $body, $secret ) {\n    $parts = &#091;];\n    foreach ( explode( ',', $header ) as $pair ) {\n        &#091; $key, $value ]  = explode( '=', $pair, 2 );\n        $parts&#091; $key ]    = $value;\n    }\n\n    $expected = hash_hmac( 'sha256', $parts&#091;'t'] . '.' . $body, $secret );\n\n    return hash_equals( $expected, $parts&#091;'v1'] );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Rotating a secret retires the old one immediately \u2014 update anywhere it&#8217;s stored before you rotate, or that receiver starts failing signature checks until it&#8217;s updated.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Managing endpoints<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each row offers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Send test<\/strong> \u2014 posts a sample event right now and shows what the receiver answered. This is the one thing on the screen that waits for a response; everything else is queued.<\/li>\n\n\n\n<li><strong>Edit<\/strong> \u2014 change the URL, description, body setting or events.<\/li>\n\n\n\n<li><strong>Disable<\/strong> \/ <strong>Enable<\/strong> \u2014 stop or resume deliveries without deleting the endpoint. A disabled endpoint keeps its secret and its event list.<\/li>\n\n\n\n<li><strong>Rotate secret<\/strong> \u2014 issues a new secret and shows it once, the same as creation. Confirmed first.<\/li>\n\n\n\n<li><strong>Delete<\/strong> \u2014 removes the endpoint and its delivery log. Confirmed first.<\/li>\n\n\n\n<li><strong>Deliveries<\/strong> \u2014 opens the delivery log for that endpoint.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Delivery, retries and the circuit breaker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Sending a webhook never blocks a ticket being saved \u2014 every delivery is queued. A failed attempt retries up to 5 times, on a backoff of roughly 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>5 consecutive failures disable the endpoint automatically.<\/strong> Rather than retrying a dead URL forever, NiroHelp switches it off and flags it in wp-admin so an administrator notices and fixes it \u2014 a URL that changed, a certificate that expired, a firewall that started blocking the request. Re-enable it from the endpoint row once it&#8217;s fixed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The delivery log<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each endpoint&#8217;s <strong>Deliveries<\/strong> view lists every attempt: <strong>When<\/strong>, <strong>Event<\/strong>, <strong>Attempt<\/strong>, <strong>Result<\/strong>, <strong>Response<\/strong>, <strong>Duration<\/strong>, and a <strong>Redeliver<\/strong> action to retry a specific one on demand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How long attempts are kept is a <strong>Settings<\/strong> field on the same screen \u2014 <strong>Keep the delivery log for<\/strong>, in days. Default 14, from 1 to 90. Older attempts, and the payloads they could be redelivered from, are deleted daily.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The payload<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every event is a JSON body: an id, the event name, a schema version, when it happened, who or what caused it, and the ticket or comment it concerns. A payload is capped at 64KB \u2014 past that, the comment body is trimmed first, then other fields, and <code>truncated<\/code> is set so a receiver can tell.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Nothing arrives.<\/strong> Check the endpoint is <strong>Active<\/strong>, not disabled by the circuit breaker. Use <strong>Send test<\/strong> first \u2014 it reports the receiver&#8217;s answer immediately, instead of waiting on the delivery log.<\/li>\n\n\n\n<li><strong>&#8220;Enter a full URL, starting with https:\/\/.&#8221;<\/strong> \u2014 the URL isn&#8217;t valid, or isn&#8217;t HTTPS. See <em>URL rules<\/em> above.<\/li>\n\n\n\n<li><strong>&#8220;That address could not be resolved, or it points at this server or a private network.&#8221;<\/strong> \u2014 NiroHelp refuses to send webhooks to itself or to a private\/internal address, the same way WordPress refuses it for any outgoing request.<\/li>\n\n\n\n<li><strong>Signature never matches.<\/strong> Recompute the HMAC over the <em>raw<\/em> request body, before any JSON re-encoding \u2014 re-serializing the body can change whitespace and break the comparison.<\/li>\n<\/ul>\n","protected":false},"author":1,"featured_media":0,"template":"","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}}},"nirohelp_product":[],"nirohelp_topic":[19],"class_list":["post-186","nirohelp_doc","type-nirohelp_doc","status-publish","hentry","nirohelp_topic-integrations"],"_links":{"self":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc\/186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc"}],"about":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/types\/nirohelp_doc"}],"author":[{"embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/users\/1"}],"wp:attachment":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/media?parent=186"}],"wp:term":[{"taxonomy":"nirohelp_product","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_product?post=186"},{"taxonomy":"nirohelp_topic","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_topic?post=186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}