{"templateId":"api_docs","sharedDataIds":{"apiDocsStore":"api-docs-webhooks-v1.yaml","sidebar":"sidebar-sidebar.yaml__webhooks-v1"},"props":{"definitionId":"webhooks-v1.yaml","settings":{"baseUrlPath":"/help/audience/engineering/webhooks-v1"},"disableAutoScroll":true,"seo":{"title":"Push Security Webhooks"},"dynamicMarkdocComponents":[],"metadata":{"type":"openapi","title":"Push Security Webhooks","version":"v1","description":"## Overview\n\nConfigure webhooks for the Push Security platform and receive real-time updates when events occur.\n\nEach webhook event has the following:\n* Versioning\n* Idempotency key\n* Metadata\n* New and old objects to show exactly what has changed\n* A signature for verifying sender authenticity\n\n## Creating webhooks\n\nTo create or manage your webhooks, go to the [Settings](https://console.pushsecurity.com/app/settings/webhooks) page in the Push admin console.\n\n## Acknowledging an event\n\nYour endpoint has 5 seconds to respond with a `200 OK` (or any other 2xx response). Otherwise, retry behavior will be triggered.\n\n## Retry behavior\n\nEach event will be sent a maximum of 4 times at the following time intervals:\n* Immediately\n* After 1 minute\n* After 5 minutes\n* After 15 minutes\n\nIf the event is acknowledged within a 5-second window, no more retries will be attempted.\n\nEach retry of the event will have a newly generated `X-Signature`, but the event `id` will be the same for all retries.\n\n## Handling duplicate events\n\nThe payload body is JSON-encoded and contains an idempotency key named `id`. If you want to ensure that you handle an event exactly once, please store this value and compare it against incoming events. This can be used to discard duplicate events that have been delivered more than once.\n\n## Verifying signatures\n\nEach event has a header `X-Signature` which contains 2 parts:\n* A UNIX timestamp value `t` (in seconds)\n* An HMAC-SHA-256 value `v1` which contains the payload signature to check using your webhook secret obtained at the time you created it\n\nHere is an example of how it is formatted:\n```\nX-Signature: t=1698349494,v1=0E01666E58BC2E6C64E9A5DA66C28CF9D88C3E342CCFC029D56B749A4B4282CE\n```\n\nTo calculate and verify the signature, perform the following steps:\n1. Parse the `X-Signature` header by splitting it first by `,` and then by `=` to obtain key-value pairs.\n2. Store the `t` (timestamp) and `v1` (signature) values in variables.\n3. Concatenate the value of `t` (as a string) with a `.` and the JSON request body (in its raw format).\n4. Use the HMAC-SHA256 algorithm to compute the hash of the concatenated string.\n5. Compare the computed HMAC with the `v1` value from the header to verify the signature.\n6. Additionally, check the timestamp (`t`) and compare it to the current time. If the difference is bigger than 35 mins (or your preferable threshold) you should discard the event to avoid replay attacks.\n\nExample in Python:\n\n```py\nimport json\nimport hmac\nimport hashlib\nimport time\n\n# Your secret key for the webhook\nSECRET_KEY = b'psws_ad9d0bba8260baf774c3821acaff1b7d'\n\n# Example header and request body (you would normally get these from the incoming HTTP request)\nexample_header = 't=1698349494,v1=0E01666E58BC2E6C64E9A5DA66C28CF9D88C3E342CCFC029D56B749A4B4282CE'\nexample_request_body = json.dumps({\"key\": \"value\"})\n\n# Step 1: Parse the header\nelements = example_header.split(',')\nparsed_header = {}\nfor element in elements:\n    key, value = element.split('=')\n    parsed_header[key] = value\n\n# Step 2: Store 't' and 'v1' values in variables\nreceived_t = parsed_header.get('t')\nreceived_v1 = parsed_header.get('v1')\n\n# Step 3: Concatenate 't' value with '.' and the JSON request body\npayload = f\"{received_t}.{example_request_body}\"\n\n# Step 4: Compute the HMAC using SHA256\ncomputed_hmac = hmac.new(SECRET_KEY, payload.encode(), hashlib.sha256).hexdigest().upper()\n\n# Step 5: Compare the signature\nis_valid = hmac.compare_digest(received_v1, computed_hmac)\n\n# Step 6: Check the timestamp\ncurrent_time = int(time.time())\ntime_difference = current_time - int(received_t)\nif time_difference > 2100:  # 35 minutes\n    is_valid = False\n    message = \"Timestamp is too old.\"\nelse:\n    message = \"Signature verified\" if is_valid else \"Signature mismatch\"\n\nprint(f\"Is the signature valid? {is_valid}. Message: {message}\")\n```\n\nExample in Node.js:\n\n```js\nconst crypto = require('crypto');\n\n// Your secret key for the webhook\nconst SECRET_KEY = 'psws_ad9d0bba8260baf774c3821acaff1b7d';\n\n// Example header and request body (you'd normally get these from the incoming HTTP request)\nconst exampleHeader = 't=1698349494,v1=0E01666E58BC2E6C64E9A5DA66C28CF9D88C3E342CCFC029D56B749A4B4282CE';\nconst exampleRequestBody = JSON.stringify({ key: 'value' });\n\n// Step 1: Parse the header\nconst elements = exampleHeader.split(',');\nconst parsedHeader = {};\nelements.forEach((element) => {\n  const [key, value] = element.split('=');\n  parsedHeader[key] = value;\n});\n\n// Step 2: Store 't' and 'v1' values in variables\nconst receivedT = parsedHeader['t'];\nconst receivedV1 = parsedHeader['v1'];\n\n// Step 3: Concatenate 't' value with '.' and the JSON request body\nconst payload = `${receivedT}.${exampleRequestBody}`;\n\n// Step 4: Compute the HMAC using SHA256\nconst computedHmac = crypto.createHmac('sha256', SECRET_KEY).update(payload).digest('hex');\n\n// Step 5: Compare the signature\nconst isValid = crypto.timingSafeEqual(Buffer.from(receivedV1, 'hex'), Buffer.from(computedHmac, 'hex'));\n\n// Step 6: Check the timestamp\nconst currentTime = Math.floor(Date.now() / 1000);\nconst timeDifference = currentTime - parseInt(receivedT, 10);\nlet message;\n\nif (timeDifference > 2100) {  // 35 minutes\n  isValid = false;\n  message = 'Timestamp is too old.';\n} else {\n  message = isValid ? 'Signature verified' : 'Signature mismatch';\n}\n\nconsole.log(`Is the signature valid? ${isValid}. Message: ${message}`);\n\n```\n\n## Versioning\n\nThe payload body is JSON-encoded and contains a value named `version`. You're currently working with version 1 of the Push Security webhooks. Should there be any breaking changes in the future, we'll bump up this version number. If you have any webhooks configured, we'll send you notifications over email about the deprecation date for the older version.\n\n## Custom headers\n\nSome SIEMs or other external systems where you may wish to send Push webhook events require a custom HTTP header for authorization. You can configure a custom header for webhooks in the Push admin console.\n\nGo to **Settings** > **Webhooks** and add a new webhook. You will see a dropdown option for **Custom headers**.\n\nThen enter a header key and value. Note that once your header key and value are entered, you will not be able to view them again, as they may contain secrets.\n\n## Filtering events\n\nYou may wish to send only specific types (or categories) of Push webhook events to your receiver. You can configure this when creating a new webhook in the Push admin console.\n\nGo to **Settings** > **Webhooks** and add a new webhook. You will see a dropdown option for **Select events**.\n\nThen you can select the specific events, or categories of events, to enable. If you select a category, any new events that are added to that category later (as part of new features released) will also be sent.\n\n## IP addresses\n\nPush sends webhook events from three static IP addresses:\n\n* 34.241.178.196\n* 54.75.174.254\n* 52.214.240.129\n\nWe recommend that you allowlist these addresses. **Note**: These addresses are within the AWS EC2 public IP range for `eu-west-1` that were used prior to March 2026. Existing customers do not need to update their allowlist unless they wish to narrow it to just these static IP addresses.\n"},"compilationErrors":[],"markdown":{"partials":{},"variables":{"rbac":{"teams":["anonymous"]},"user":{},"remoteAddr":{"hostname":"push-security-prd-ba8f0f76-a2d2-42f5-aea2-d421.redocly.app","port":4000,"ipAddress":"216.73.216.190"},"lang":"default_locale","env":{"PUBLIC_REDOCLY_BRANCH_NAME":"main"}}},"pagePropGetterError":{"message":"","name":""}},"slug":"/webhooks-v1","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}