{"slug":"webhooks","title":"Webhooks","tags":["tailscale"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Webhooks\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nThis featureis available for [all plans](https://tailscale.com/pricing).\r\n\r\nWebhooks let you subscribe to certain events on your Tailscale network and process the event notifications through an integration or app. For example, you could integrate Tailscale events with a Slack channel. If subscribed to an event such as adding a node, your webhook endpoint can send a message in a Slack channel anytime a node is added to your tailnet.\r\n\r\n![Tailscale events in a Slack channel](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftailscale-events-slack.01bdd3d7.png&w=3840&q=75)\r\n\r\n## [How it works](https://tailscale.com/docs/features/webhooks\\#how-it-works)\r\n\r\nYou provide a webhook endpoint which can receive HTTPS POST requests for subscribed Tailscale events. The body of the request provides information about the event. It is up to you to determine how the webhook should process a notification. Tailscale typically sends an event notification to a webhook within a few seconds of the event's occurrence.\r\n\r\nYou create and manage webhook endpoints in the [Webhooks](https://login.tailscale.com/admin/settings/webhooks) page of the admin console.\r\n\r\nTailscale provides a digital signature for events that it sends, so you can verify whether an event was signed by a secret that is shared between you and Tailscale.\r\n\r\nWebhook events are sent as JSON objects, with the format described in [events payload](https://tailscale.com/docs/features/webhooks#events-payload). Optionally, you can configure webhooks to send events in a compatible format for the following destinations:\r\n\r\n| Destination | Destination's webhook documentation |\r\n| --- | --- |\r\n| Discord | [Intro to Discord webhooks](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) |\r\n| Google Chat | [Send messages to Google Chat with incoming webhooks](https://developers.google.com/chat/how-tos/webhooks) |\r\n| Mattermost | [Mattermost webhooks](https://developers.mattermost.com/integrate/webhooks) |\r\n| Slack | [Sending Slack messages using incoming webhooks](https://api.slack.com/messaging/webhooks) |\r\n\r\n## [Prerequisites](https://tailscale.com/docs/features/webhooks\\#prerequisites)\r\n\r\n- You need a webhook endpoint which will process the Tailscale event notifications.\r\n\r\n- Your webhook endpoint must be able to process HTTPS POST requests and must use either port `80` or port `443`.\r\n\r\n- You need to be an [Owner, Admin, Network admin, or IT admin](https://tailscale.com/docs/reference/user-roles) of a tailnet to create, modify, or delete webhooks.\r\n\r\n\r\n## [Setting up a webhook endpoint](https://tailscale.com/docs/features/webhooks\\#setting-up-a-webhook-endpoint)\r\n\r\nWebhooks apply to a tailnet. If one user creates a webhook, other [Owner, Admin, Network admin, or IT admin](https://tailscale.com/docs/reference/user-roles) users in the tailnet can modify or delete it. If a webhook is created by a user who is later removed or suspended from your tailnet, the webhook will still work.\r\n\r\n1. Open the [Webhooks](https://login.tailscale.com/admin/settings/webhooks) page of the admin console.\r\n\r\n2. Select **Add endpoint**.\r\n\r\n3. In the **Add endpoint** page:\r\n1. For **Webhook URL**, provide the endpoint for your webhook. The endpoint URL must use the HTTPS protocol, and must use either port `80` or port `443`.\r\n\r\n2. (Optional) For **Destination**, select the destination for the endpoint. Tailscale will send your webhook events in the format expected by the destination. (If you enter a Discord or Slack URL for **Webhook URL**, then the **Destination** field will be grayed out with the respective destination selected.)\r\n      ![The webhooks 'Webhook URL' UI in the admin console](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fadd-webhook-endpoint.e488686c.png&w=1080&q=75)\r\n      Choose **None** if you are using the general Tailscale format for the events.\r\n\r\n3. Select the event categories or events you want to receive as notifications.\r\n\r\n      A category is a set of related events. For example, the **Tailnet Management** category contains events related to node actions and [tailnet policy file](https://tailscale.com/docs/reference/syntax/policy-file) updates. If you select a category, you will be subscribed to any new events that Tailscale adds to the category in the future.\r\n\r\n      If you don't want to use categories, you can select specific events.\r\n      ![The webhooks 'Events' UI in the admin console](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwebhook-events.b12a00fe.png&w=1080&q=75)\r\n4. Select **Add endpoint**.\r\n4. On the subsequent **Webhook secret** popup, select **Copy** to copy the newly-created webhook secret. After you close the **Webhook secret** page, you won't be able to copy the secret again. Also, note that Tailscale-generated webhook secrets are case-sensitive.\r\n\r\n\r\n\r\nStore the webhook secret securely.\r\n\r\n5. Select **Done**.\r\n\r\nYour webhook endpoint is now configured.\r\n\r\n6. To ensure your webhook is configured correctly and can receive events from Tailscale, [test your webhook](https://tailscale.com/docs/features/webhooks#testing-your-webhook).\r\n\r\n\r\n## [Events](https://tailscale.com/docs/features/webhooks\\#events)\r\n\r\nYou can subscribe to the following events:\r\n\r\n| Category | Event | Description |\r\n| --- | --- | --- |\r\n| Device misconfiguration | `exitNodeIPForwardingNotEnabled` | [Exit node](https://tailscale.com/docs/features/exit-nodes) has [IP forwarding](https://tailscale.com/docs/features/exit-nodes?tab=linux#advertise-a-device-as-an-exit-node) disabled. |\r\n| Device misconfiguration | `subnetIPForwardingNotEnabled` | [Subnet](https://tailscale.com/docs/features/subnet-routers) has [IP forwarding](https://tailscale.com/docs/features/subnet-routers?tab=linux#enable-ip-forwarding) disabled. |\r\n| Tailnet management | `nodeApproved` | Node was approved. |\r\n| Tailnet management | `nodeAuthorized` (deprecated) | Node was authorized. This event is deprecated and is replaced by `nodeApproved`. Your endpoint will continue to receive this event until you turn it off. |\r\n| Tailnet management | `nodeCreated` | Node was created. |\r\n| Tailnet management | `nodeDeleted` | Node was deleted. This includes automatic deletion of [ephemeral nodes](https://tailscale.com/docs/features/ephemeral-nodes). |\r\n| Tailnet management | `nodeKeyExpired` | Node key recently expired. |\r\n| Tailnet management | `nodeKeyExpiringInOneDay` | [Node key](https://tailscale.com/blog/tailscale-key-management#node-keys) is going to expire in less than one day. |\r\n| Tailnet management | `nodeNeedsApproval` | Node needs [approval](https://tailscale.com/docs/features/access-control/device-management/device-approval). Note that any [pre-approved nodes](https://tailscale.com/docs/features/access-control/device-management/device-approval#pre-approve-devices-with-an-auth-key) will not generate the `nodeApproved` event. |\r\n| Tailnet management | `nodeNeedsAuthorization` (deprecated) | Node needs authorization. This event is deprecated and is replaced by `nodeNeedsApproval`. Your endpoint will continue to receive this event until you turn it off. |\r\n| Tailnet management | `nodeNeedsSignature` | Node needs a signature from a trusted node. This event is only sent for nodes that belong to tailnets that enabled [Tailnet Lock](https://tailscale.com/docs/features/tailnet-lock), and will not be sent for shared nodes. |\r\n| Tailnet management | `nodeSigned` | Node was signed by a trusted node. This applies to tailnets that enabled Tailnet Lock. |\r\n| Tailnet management | `policyUpdate` | [Tailnet policy file](https://tailscale.com/docs/features/tailnet-policy-file) updated. |\r\n| Tailnet management | `userApproved` | User was approved. |\r\n| Tailnet management | `userCreated` | User was created. |\r\n| Tailnet management | `userNeedsApproval` | User needs [approval](https://tailscale.com/docs/features/access-control/user-approval). |\r\n| Tailnet management | `userRoleUpdated` | User role was [changed](https://tailscale.com/docs/features/sharing/how-to/change-role). |\r\n| Webhook management | `test` | Webhook test event was generated. |\r\n| Webhook management | `webhookDeleted` | Webhook was deleted. This event is subscribed by default and cannot be disabled. |\r\n| Webhook management | `webhookUpdated` | Webhook was updated. This event is subscribed by default and cannot be disabled. |\r\n\r\n## [Events payload](https://tailscale.com/docs/features/webhooks\\#events-payload)\r\n\r\nWebhook events are sent as JSON objects with the following fields:\r\n\r\n- `timestamp`: Time the event occurred, formatted as a RFC 3339 string.\r\n- `version`: Version of the event payload.\r\n- `type`: Type of the event (as listed in the table above).\r\n- `tailnet`: Name of the tailnet where the event occurred.\r\n- `message`: Human-readable summary of the event.\r\n- `data` (Optional): Per-event payload with additional data. Most events will have an `actor` field that identifies the user or automated process that did the action that triggered the event. It is the same actor that is included in [configuration audit log entries](https://tailscale.com/docs/features/logging/audit-logging#log-structure).\r\n\r\nMultiple events may be sent in one payload to minimize overhead. The root payload object is always an array of events.\r\n\r\nThe following shows an example events payload sent to a webhook endpoint:\r\n\r\n```json\r\n[\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:37:51.658918-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"test\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"This is a test event\",\\\r\n    \"data\": null\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:02.949217-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeCreated\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net created\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:02.949278-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeNeedsApproval\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net needs approval\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:15.966728-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeApproved\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net approved\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2023-04-21T13:59:15.966728-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeDeleted\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net deleted\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-27T09:51:46.512946-07:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"policyUpdate\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Tailnet policy file updated\",\\\r\n    \"data\": {\\\r\n      \"newPolicy\": \"{\\n\\t\\\"acls\\\": [\\n\\t\\t{\\\"action\\\": \\\"accept\\\", \\\"src\\\": [\\\"autogroup:member\\\"], \\\"dst\\\": [\\\"*:*\\\"]},\\n\\t],\\n}\",\\\r\n      \"oldPolicy\": \"{\\n\\t\\\"acls\\\": [\\n\\t\\t{\\\"action\\\": \\\"accept\\\", \\\"src\\\": [\\\"*\\\"], \\\"dst\\\": [\\\"*:*\\\"]},\\n\\t],\\n}\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/acls\",\\\r\n      \"actor\": \"admin@example.com\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-11-08T10:26:08.775392-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeKeyExpiringInOneDay\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net key expiring in less than one day\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"expiring-node-key-marker\",\\\r\n      \"expiration\": \"2022-11-08T18:44:46.979292Z\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-11-08T10:45:08.775392-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeKeyExpired\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net key recently expired\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"expiring-node-key-marker\",\\\r\n      \"expiration\": \"2022-11-08T18:44:46.979292Z\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2023-02-27T11:49:25.208092-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"userRoleUpdated\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"User alice@example.com role changed\",\\\r\n    \"data\": {\\\r\n      \"user\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/users?q=alice%40example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"oldRoles\": [\"Member\"],\\\r\n      \"newRoles\": [\"Member\", \"IT admin\"]\\\r\n    }\\\r\n  }\\\r\n]\r\n```\r\n\r\n## [Testing your webhook](https://tailscale.com/docs/features/webhooks\\#testing-your-webhook)\r\n\r\nTo ensure an endpoint is properly configured and able to receive events from Tailscale, you can send a test event:\r\n\r\n1. Open the [Webhooks](https://login.tailscale.com/admin/settings/webhooks) page of the admin console.\r\n\r\n2. Find the endpoint that you want to test, select the ![ellipsis icon](https://tailscale.com/files/images/icons/fa-ellipsis-h.svg) menu to the right of the page, and select **Test endpoint**.\r\n\r\n3. Select **Send test event**. If your webhook is configured correctly, within a few seconds your webhook endpoint should receive an event with type of \"test\".\r\n\r\n\r\n## [Retries for events that fail to send](https://tailscale.com/docs/features/webhooks\\#retries-for-events-that-fail-to-send)\r\n\r\nTailscale typically sends an event notification to a webhook within a few seconds of the event's occurrence. If an event notification fails to successfully send (such as when Tailscale receives a `3xx`, `4xx`, or `5xx` error, or no response at all from your webhook endpoint), Tailscale will retry sending the event. For an event that fails to send, we'll retry sending the event hourly, up to a maximum of 24 hours.\r\n\r\n## [Updating subscribed events](https://tailscale.com/docs/features/webhooks\\#updating-subscribed-events)\r\n\r\n1. Open the [Webhooks](https://login.tailscale.com/admin/settings/webhooks) page of the admin console.\r\n\r\n2. Find the endpoint that you want to update, select the ![ellipsis icon](https://tailscale.com/files/images/icons/fa-ellipsis-h.svg) menu to the right of the page, and select **Edit**.\r\n\r\n3. Select the events you want to receive as notifications, and deselect those you don't want to receive.\r\n\r\n4. Select **Edit endpoint**.\r\n\r\n\r\n## [Webhook secret](https://tailscale.com/docs/features/webhooks\\#webhook-secret)\r\n\r\nThe webhook secret is a signing secret shared between Tailscale and the creator of the webhook endpoint, and is unique per endpoint. If this shared secret is compromised or leaked, whomever knows the secret can send fake events. If you suspect your secret is compromised, [create a new secret](https://tailscale.com/docs/features/webhooks/how-to/rotate-webhook-secret).\r\n\r\nThe webhook secret has no expiry.\r\n\r\n## [Rotating a webhook secret](https://tailscale.com/docs/features/webhooks\\#rotating-a-webhook-secret)\r\n\r\nRefer to [rotate a new webhook secret](https://tailscale.com/docs/features/webhooks/how-to/rotate-webhook-secret) to create a new webhook secret. After you create the new secret, update your webhook endpoint to use the new secret.\r\n\r\n## [Deleting an endpoint](https://tailscale.com/docs/features/webhooks\\#deleting-an-endpoint)\r\n\r\n1. Open the [Webhooks](https://login.tailscale.com/admin/settings/webhooks) page of the admin console.\r\n\r\n2. Find the endpoint that you want to delete, select the ![ellipsis icon](https://tailscale.com/files/images/icons/fa-ellipsis-h.svg) menu to the right of the page, and select **Delete**.\r\n\r\n3. Select **Delete endpoint** to confirm you want to delete the endpoint.\r\n\r\n\r\n## [Using a new webhook for an existing endpoint](https://tailscale.com/docs/features/webhooks\\#using-a-new-webhook-for-an-existing-endpoint)\r\n\r\nTo add a new webhook (subscribed event) for an existing endpoint, [edit the endpoint](https://tailscale.com/docs/features/webhooks#updating-subscribed-events) instead of setting up a new endpoint.\r\n\r\n## [Audit logging of webhook actions](https://tailscale.com/docs/features/webhooks\\#audit-logging-of-webhook-actions)\r\n\r\nIn [configuration audit logging](https://tailscale.com/docs/features/logging/audit-logging), an action will be recorded in your audit log whenever a webhook is created, deleted, or updated. The log entry will show who performed the action, and when the action occurred.\r\n\r\n## [Verifying an event signature](https://tailscale.com/docs/features/webhooks\\#verifying-an-event-signature)\r\n\r\nYou can verify whether an event was signed by the [webhook secret](https://tailscale.com/docs/features/webhooks#webhook-secret) that was shared between you and Tailscale. Note this doesn't necessarily mean that an event was sent from Tailscale. Rather, it means an event was sent from an entity that has knowledge of the secret shared between you and Tailscale.\r\n\r\nA event sent from Tailscale contains a `Tailscale-Webhook-Signature` header. The `Tailscale-Webhook-Signature` header includes a timestamp and a signature:\r\n\r\n```markup\r\nTailscale-Webhook-Signature:t=1663781880,v1=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\r\n```\r\n\r\nThe timestamp, prefixed by `t=`, is the epoch time in seconds when the event occurred. The signature, prefixed by `v1=`, is a hash-based message authentication code (HMAC) using SHA-256. The only supported scheme for the signature is `v1`.\r\n\r\nMost modern programming languages provide libraries for computing and comparing HMACs. The following flow describes how to verify a signature.\r\n\r\n1. Parse the event timestamp and signature from the `Tailscale-Webhook-Signature` header.\r\n\r\nUsing the `,` character as the separator, split the `Tailscale-Webhook-Signature` data into a list of elements. Then, using the `=` character, split each element to get two key-value pairs. The first key is `t`, with the event timestamp as its value. The second key is `v1`, with the event signature as its value.\r\n\r\n2. Compare the event timestamp (the value for `t`) with the current time. For example, if your verification process acts on events as soon as they are received, and if the event time is more than 5 minutes prior to the current time, you might consider the event as a replay attack.\r\n\r\n3. Create a string, `string_to_sign`, to sign by concatenating:\r\n   - The timestamp (the value of `t`) represented as a string.\r\n   - The `.` character.\r\n   - The decoded event request body. Note this is in the request itself, not in the `Tailscale-Webhook-Signature` header. The request body contains the encoded [events payload](https://tailscale.com/docs/features/webhooks#events-payload), you need to decode the request body for signing purposes.\r\n4. Compute the signature of `string_to_sign`.\r\n\r\nCreate an HMAC with the SHA256 hash function. Use your webhook secret for the signing key, and use `string_to_sign` as the message to sign.\r\n\r\n5. Use an HMAC compare function to compare the signature in the `Tailscale-Webhook-Signature` header (the value of `v1`) with the signature you created in the previous step. If they are not identical, that indicates the event's payload was not signed by your webhook's secret, and the event should not be considered an event sent from Tailscale.\r\n\r\n\r\n### [Sample verification code](https://tailscale.com/docs/features/webhooks\\#sample-verification-code)\r\n\r\nCheck out our [example Go code](https://github.com/tailscale/tailscale/blob/main/docs/webhooks/example.go) for help verifying the signature.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Webhooks</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>This featureis available for <a href=\"https://tailscale.com/pricing\">all plans</a>.</p>\n<p>Webhooks let you subscribe to certain events on your Tailscale network and process the event notifications through an integration or app. For example, you could integrate Tailscale events with a Slack channel. If subscribed to an event such as adding a node, your webhook endpoint can send a message in a Slack channel anytime a node is added to your tailnet.</p>\n<p><img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftailscale-events-slack.01bdd3d7.png&#x26;w=3840&#x26;q=75\" alt=\"Tailscale events in a Slack channel\"></p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#how-it-works\">How it works</a></h2>\n<p>You provide a webhook endpoint which can receive HTTPS POST requests for subscribed Tailscale events. The body of the request provides information about the event. It is up to you to determine how the webhook should process a notification. Tailscale typically sends an event notification to a webhook within a few seconds of the event's occurrence.</p>\n<p>You create and manage webhook endpoints in the <a href=\"https://login.tailscale.com/admin/settings/webhooks\">Webhooks</a> page of the admin console.</p>\n<p>Tailscale provides a digital signature for events that it sends, so you can verify whether an event was signed by a secret that is shared between you and Tailscale.</p>\n<p>Webhook events are sent as JSON objects, with the format described in <a href=\"https://tailscale.com/docs/features/webhooks#events-payload\">events payload</a>. Optionally, you can configure webhooks to send events in a compatible format for the following destinations:</p>\n<p>| Destination | Destination's webhook documentation |\r\n| --- | --- |\r\n| Discord | <a href=\"https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks\">Intro to Discord webhooks</a> |\r\n| Google Chat | <a href=\"https://developers.google.com/chat/how-tos/webhooks\">Send messages to Google Chat with incoming webhooks</a> |\r\n| Mattermost | <a href=\"https://developers.mattermost.com/integrate/webhooks\">Mattermost webhooks</a> |\r\n| Slack | <a href=\"https://api.slack.com/messaging/webhooks\">Sending Slack messages using incoming webhooks</a> |</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#prerequisites\">Prerequisites</a></h2>\n<ul>\n<li>\n<p>You need a webhook endpoint which will process the Tailscale event notifications.</p>\n</li>\n<li>\n<p>Your webhook endpoint must be able to process HTTPS POST requests and must use either port <code>80</code> or port <code>443</code>.</p>\n</li>\n<li>\n<p>You need to be an <a href=\"https://tailscale.com/docs/reference/user-roles\">Owner, Admin, Network admin, or IT admin</a> of a tailnet to create, modify, or delete webhooks.</p>\n</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#setting-up-a-webhook-endpoint\">Setting up a webhook endpoint</a></h2>\n<p>Webhooks apply to a tailnet. If one user creates a webhook, other <a href=\"https://tailscale.com/docs/reference/user-roles\">Owner, Admin, Network admin, or IT admin</a> users in the tailnet can modify or delete it. If a webhook is created by a user who is later removed or suspended from your tailnet, the webhook will still work.</p>\n<ol>\n<li>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/webhooks\">Webhooks</a> page of the admin console.</p>\n</li>\n<li>\n<p>Select <strong>Add endpoint</strong>.</p>\n</li>\n<li>\n<p>In the <strong>Add endpoint</strong> page:</p>\n</li>\n<li>\n<p>For <strong>Webhook URL</strong>, provide the endpoint for your webhook. The endpoint URL must use the HTTPS protocol, and must use either port <code>80</code> or port <code>443</code>.</p>\n</li>\n<li>\n<p>(Optional) For <strong>Destination</strong>, select the destination for the endpoint. Tailscale will send your webhook events in the format expected by the destination. (If you enter a Discord or Slack URL for <strong>Webhook URL</strong>, then the <strong>Destination</strong> field will be grayed out with the respective destination selected.)\r\n<img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fadd-webhook-endpoint.e488686c.png&#x26;w=1080&#x26;q=75\" alt=\"The webhooks &#x27;Webhook URL&#x27; UI in the admin console\">\r\nChoose <strong>None</strong> if you are using the general Tailscale format for the events.</p>\n</li>\n<li>\n<p>Select the event categories or events you want to receive as notifications.</p>\n<p>A category is a set of related events. For example, the <strong>Tailnet Management</strong> category contains events related to node actions and <a href=\"https://tailscale.com/docs/reference/syntax/policy-file\">tailnet policy file</a> updates. If you select a category, you will be subscribed to any new events that Tailscale adds to the category in the future.</p>\n<p>If you don't want to use categories, you can select specific events.\r\n<img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwebhook-events.b12a00fe.png&#x26;w=1080&#x26;q=75\" alt=\"The webhooks &#x27;Events&#x27; UI in the admin console\"></p>\n</li>\n<li>\n<p>Select <strong>Add endpoint</strong>.</p>\n</li>\n<li>\n<p>On the subsequent <strong>Webhook secret</strong> popup, select <strong>Copy</strong> to copy the newly-created webhook secret. After you close the <strong>Webhook secret</strong> page, you won't be able to copy the secret again. Also, note that Tailscale-generated webhook secrets are case-sensitive.</p>\n</li>\n</ol>\n<p>Store the webhook secret securely.</p>\n<ol start=\"5\">\n<li>Select <strong>Done</strong>.</li>\n</ol>\n<p>Your webhook endpoint is now configured.</p>\n<ol start=\"6\">\n<li>To ensure your webhook is configured correctly and can receive events from Tailscale, <a href=\"https://tailscale.com/docs/features/webhooks#testing-your-webhook\">test your webhook</a>.</li>\n</ol>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#events\">Events</a></h2>\n<p>You can subscribe to the following events:</p>\n<p>| Category | Event | Description |\r\n| --- | --- | --- |\r\n| Device misconfiguration | <code>exitNodeIPForwardingNotEnabled</code> | <a href=\"https://tailscale.com/docs/features/exit-nodes\">Exit node</a> has <a href=\"https://tailscale.com/docs/features/exit-nodes?tab=linux#advertise-a-device-as-an-exit-node\">IP forwarding</a> disabled. |\r\n| Device misconfiguration | <code>subnetIPForwardingNotEnabled</code> | <a href=\"https://tailscale.com/docs/features/subnet-routers\">Subnet</a> has <a href=\"https://tailscale.com/docs/features/subnet-routers?tab=linux#enable-ip-forwarding\">IP forwarding</a> disabled. |\r\n| Tailnet management | <code>nodeApproved</code> | Node was approved. |\r\n| Tailnet management | <code>nodeAuthorized</code> (deprecated) | Node was authorized. This event is deprecated and is replaced by <code>nodeApproved</code>. Your endpoint will continue to receive this event until you turn it off. |\r\n| Tailnet management | <code>nodeCreated</code> | Node was created. |\r\n| Tailnet management | <code>nodeDeleted</code> | Node was deleted. This includes automatic deletion of <a href=\"https://tailscale.com/docs/features/ephemeral-nodes\">ephemeral nodes</a>. |\r\n| Tailnet management | <code>nodeKeyExpired</code> | Node key recently expired. |\r\n| Tailnet management | <code>nodeKeyExpiringInOneDay</code> | <a href=\"https://tailscale.com/blog/tailscale-key-management#node-keys\">Node key</a> is going to expire in less than one day. |\r\n| Tailnet management | <code>nodeNeedsApproval</code> | Node needs <a href=\"https://tailscale.com/docs/features/access-control/device-management/device-approval\">approval</a>. Note that any <a href=\"https://tailscale.com/docs/features/access-control/device-management/device-approval#pre-approve-devices-with-an-auth-key\">pre-approved nodes</a> will not generate the <code>nodeApproved</code> event. |\r\n| Tailnet management | <code>nodeNeedsAuthorization</code> (deprecated) | Node needs authorization. This event is deprecated and is replaced by <code>nodeNeedsApproval</code>. Your endpoint will continue to receive this event until you turn it off. |\r\n| Tailnet management | <code>nodeNeedsSignature</code> | Node needs a signature from a trusted node. This event is only sent for nodes that belong to tailnets that enabled <a href=\"https://tailscale.com/docs/features/tailnet-lock\">Tailnet Lock</a>, and will not be sent for shared nodes. |\r\n| Tailnet management | <code>nodeSigned</code> | Node was signed by a trusted node. This applies to tailnets that enabled Tailnet Lock. |\r\n| Tailnet management | <code>policyUpdate</code> | <a href=\"https://tailscale.com/docs/features/tailnet-policy-file\">Tailnet policy file</a> updated. |\r\n| Tailnet management | <code>userApproved</code> | User was approved. |\r\n| Tailnet management | <code>userCreated</code> | User was created. |\r\n| Tailnet management | <code>userNeedsApproval</code> | User needs <a href=\"https://tailscale.com/docs/features/access-control/user-approval\">approval</a>. |\r\n| Tailnet management | <code>userRoleUpdated</code> | User role was <a href=\"https://tailscale.com/docs/features/sharing/how-to/change-role\">changed</a>. |\r\n| Webhook management | <code>test</code> | Webhook test event was generated. |\r\n| Webhook management | <code>webhookDeleted</code> | Webhook was deleted. This event is subscribed by default and cannot be disabled. |\r\n| Webhook management | <code>webhookUpdated</code> | Webhook was updated. This event is subscribed by default and cannot be disabled. |</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#events-payload\">Events payload</a></h2>\n<p>Webhook events are sent as JSON objects with the following fields:</p>\n<ul>\n<li><code>timestamp</code>: Time the event occurred, formatted as a RFC 3339 string.</li>\n<li><code>version</code>: Version of the event payload.</li>\n<li><code>type</code>: Type of the event (as listed in the table above).</li>\n<li><code>tailnet</code>: Name of the tailnet where the event occurred.</li>\n<li><code>message</code>: Human-readable summary of the event.</li>\n<li><code>data</code> (Optional): Per-event payload with additional data. Most events will have an <code>actor</code> field that identifies the user or automated process that did the action that triggered the event. It is the same actor that is included in <a href=\"https://tailscale.com/docs/features/logging/audit-logging#log-structure\">configuration audit log entries</a>.</li>\n</ul>\n<p>Multiple events may be sent in one payload to minimize overhead. The root payload object is always an array of events.</p>\n<p>The following shows an example events payload sent to a webhook endpoint:</p>\n<pre><code class=\"language-json\">[\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:37:51.658918-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"test\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"This is a test event\",\\\r\n    \"data\": null\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:02.949217-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeCreated\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net created\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:02.949278-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeNeedsApproval\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net needs approval\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-21T13:59:15.966728-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeApproved\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net approved\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2023-04-21T13:59:15.966728-04:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeDeleted\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net deleted\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-09-27T09:51:46.512946-07:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"policyUpdate\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Tailnet policy file updated\",\\\r\n    \"data\": {\\\r\n      \"newPolicy\": \"{\\n\\t\\\"acls\\\": [\\n\\t\\t{\\\"action\\\": \\\"accept\\\", \\\"src\\\": [\\\"autogroup:member\\\"], \\\"dst\\\": [\\\"*:*\\\"]},\\n\\t],\\n}\",\\\r\n      \"oldPolicy\": \"{\\n\\t\\\"acls\\\": [\\n\\t\\t{\\\"action\\\": \\\"accept\\\", \\\"src\\\": [\\\"*\\\"], \\\"dst\\\": [\\\"*:*\\\"]},\\n\\t],\\n}\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/acls\",\\\r\n      \"actor\": \"admin@example.com\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-11-08T10:26:08.775392-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeKeyExpiringInOneDay\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net key expiring in less than one day\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"expiring-node-key-marker\",\\\r\n      \"expiration\": \"2022-11-08T18:44:46.979292Z\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2022-11-08T10:45:08.775392-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"nodeKeyExpired\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"Node alice-workstation1.yak-bebop.ts.net key recently expired\",\\\r\n    \"data\": {\\\r\n      \"nodeID\": \"nFJw3SRKTM59\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/machines/100.12.345.67\",\\\r\n      \"deviceName\": \"alice-workstation1.yak-bebop.ts.net\",\\\r\n      \"managedBy\": \"alice@example.com\",\\\r\n      \"actor\": \"expiring-node-key-marker\",\\\r\n      \"expiration\": \"2022-11-08T18:44:46.979292Z\"\\\r\n    }\\\r\n  },\\\r\n  {\\\r\n    \"timestamp\": \"2023-02-27T11:49:25.208092-08:00\",\\\r\n    \"version\": 1,\\\r\n    \"type\": \"userRoleUpdated\",\\\r\n    \"tailnet\": \"example.com\",\\\r\n    \"message\": \"User alice@example.com role changed\",\\\r\n    \"data\": {\\\r\n      \"user\": \"alice@example.com\",\\\r\n      \"url\": \"https://login.tailscale.com/admin/users?q=alice%40example.com\",\\\r\n      \"actor\": \"admin@example.com\",\\\r\n      \"oldRoles\": [\"Member\"],\\\r\n      \"newRoles\": [\"Member\", \"IT admin\"]\\\r\n    }\\\r\n  }\\\r\n]\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#testing-your-webhook\">Testing your webhook</a></h2>\n<p>To ensure an endpoint is properly configured and able to receive events from Tailscale, you can send a test event:</p>\n<ol>\n<li>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/webhooks\">Webhooks</a> page of the admin console.</p>\n</li>\n<li>\n<p>Find the endpoint that you want to test, select the <img src=\"https://tailscale.com/files/images/icons/fa-ellipsis-h.svg\" alt=\"ellipsis icon\"> menu to the right of the page, and select <strong>Test endpoint</strong>.</p>\n</li>\n<li>\n<p>Select <strong>Send test event</strong>. If your webhook is configured correctly, within a few seconds your webhook endpoint should receive an event with type of \"test\".</p>\n</li>\n</ol>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#retries-for-events-that-fail-to-send\">Retries for events that fail to send</a></h2>\n<p>Tailscale typically sends an event notification to a webhook within a few seconds of the event's occurrence. If an event notification fails to successfully send (such as when Tailscale receives a <code>3xx</code>, <code>4xx</code>, or <code>5xx</code> error, or no response at all from your webhook endpoint), Tailscale will retry sending the event. For an event that fails to send, we'll retry sending the event hourly, up to a maximum of 24 hours.</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#updating-subscribed-events\">Updating subscribed events</a></h2>\n<ol>\n<li>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/webhooks\">Webhooks</a> page of the admin console.</p>\n</li>\n<li>\n<p>Find the endpoint that you want to update, select the <img src=\"https://tailscale.com/files/images/icons/fa-ellipsis-h.svg\" alt=\"ellipsis icon\"> menu to the right of the page, and select <strong>Edit</strong>.</p>\n</li>\n<li>\n<p>Select the events you want to receive as notifications, and deselect those you don't want to receive.</p>\n</li>\n<li>\n<p>Select <strong>Edit endpoint</strong>.</p>\n</li>\n</ol>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#webhook-secret\">Webhook secret</a></h2>\n<p>The webhook secret is a signing secret shared between Tailscale and the creator of the webhook endpoint, and is unique per endpoint. If this shared secret is compromised or leaked, whomever knows the secret can send fake events. If you suspect your secret is compromised, <a href=\"https://tailscale.com/docs/features/webhooks/how-to/rotate-webhook-secret\">create a new secret</a>.</p>\n<p>The webhook secret has no expiry.</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#rotating-a-webhook-secret\">Rotating a webhook secret</a></h2>\n<p>Refer to <a href=\"https://tailscale.com/docs/features/webhooks/how-to/rotate-webhook-secret\">rotate a new webhook secret</a> to create a new webhook secret. After you create the new secret, update your webhook endpoint to use the new secret.</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#deleting-an-endpoint\">Deleting an endpoint</a></h2>\n<ol>\n<li>\n<p>Open the <a href=\"https://login.tailscale.com/admin/settings/webhooks\">Webhooks</a> page of the admin console.</p>\n</li>\n<li>\n<p>Find the endpoint that you want to delete, select the <img src=\"https://tailscale.com/files/images/icons/fa-ellipsis-h.svg\" alt=\"ellipsis icon\"> menu to the right of the page, and select <strong>Delete</strong>.</p>\n</li>\n<li>\n<p>Select <strong>Delete endpoint</strong> to confirm you want to delete the endpoint.</p>\n</li>\n</ol>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#using-a-new-webhook-for-an-existing-endpoint\">Using a new webhook for an existing endpoint</a></h2>\n<p>To add a new webhook (subscribed event) for an existing endpoint, <a href=\"https://tailscale.com/docs/features/webhooks#updating-subscribed-events\">edit the endpoint</a> instead of setting up a new endpoint.</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#audit-logging-of-webhook-actions\">Audit logging of webhook actions</a></h2>\n<p>In <a href=\"https://tailscale.com/docs/features/logging/audit-logging\">configuration audit logging</a>, an action will be recorded in your audit log whenever a webhook is created, deleted, or updated. The log entry will show who performed the action, and when the action occurred.</p>\n<h2><a href=\"https://tailscale.com/docs/features/webhooks#verifying-an-event-signature\">Verifying an event signature</a></h2>\n<p>You can verify whether an event was signed by the <a href=\"https://tailscale.com/docs/features/webhooks#webhook-secret\">webhook secret</a> that was shared between you and Tailscale. Note this doesn't necessarily mean that an event was sent from Tailscale. Rather, it means an event was sent from an entity that has knowledge of the secret shared between you and Tailscale.</p>\n<p>A event sent from Tailscale contains a <code>Tailscale-Webhook-Signature</code> header. The <code>Tailscale-Webhook-Signature</code> header includes a timestamp and a signature:</p>\n<pre><code class=\"language-markup\">Tailscale-Webhook-Signature:t=1663781880,v1=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n</code></pre>\n<p>The timestamp, prefixed by <code>t=</code>, is the epoch time in seconds when the event occurred. The signature, prefixed by <code>v1=</code>, is a hash-based message authentication code (HMAC) using SHA-256. The only supported scheme for the signature is <code>v1</code>.</p>\n<p>Most modern programming languages provide libraries for computing and comparing HMACs. The following flow describes how to verify a signature.</p>\n<ol>\n<li>Parse the event timestamp and signature from the <code>Tailscale-Webhook-Signature</code> header.</li>\n</ol>\n<p>Using the <code>,</code> character as the separator, split the <code>Tailscale-Webhook-Signature</code> data into a list of elements. Then, using the <code>=</code> character, split each element to get two key-value pairs. The first key is <code>t</code>, with the event timestamp as its value. The second key is <code>v1</code>, with the event signature as its value.</p>\n<ol start=\"2\">\n<li>\n<p>Compare the event timestamp (the value for <code>t</code>) with the current time. For example, if your verification process acts on events as soon as they are received, and if the event time is more than 5 minutes prior to the current time, you might consider the event as a replay attack.</p>\n</li>\n<li>\n<p>Create a string, <code>string_to_sign</code>, to sign by concatenating:</p>\n<ul>\n<li>The timestamp (the value of <code>t</code>) represented as a string.</li>\n<li>The <code>.</code> character.</li>\n<li>The decoded event request body. Note this is in the request itself, not in the <code>Tailscale-Webhook-Signature</code> header. The request body contains the encoded <a href=\"https://tailscale.com/docs/features/webhooks#events-payload\">events payload</a>, you need to decode the request body for signing purposes.</li>\n</ul>\n</li>\n<li>\n<p>Compute the signature of <code>string_to_sign</code>.</p>\n</li>\n</ol>\n<p>Create an HMAC with the SHA256 hash function. Use your webhook secret for the signing key, and use <code>string_to_sign</code> as the message to sign.</p>\n<ol start=\"5\">\n<li>Use an HMAC compare function to compare the signature in the <code>Tailscale-Webhook-Signature</code> header (the value of <code>v1</code>) with the signature you created in the previous step. If they are not identical, that indicates the event's payload was not signed by your webhook's secret, and the event should not be considered an event sent from Tailscale.</li>\n</ol>\n<h3><a href=\"https://tailscale.com/docs/features/webhooks#sample-verification-code\">Sample verification code</a></h3>\n<p>Check out our <a href=\"https://github.com/tailscale/tailscale/blob/main/docs/webhooks/example.go\">example Go code</a> for help verifying the signature.</p>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}