NAV

Webhooks

Webhooks enable you to build your own real-time integrations that subscribe to certain report and program events on HackerOne. They can be used to:

When one of those events is triggered, we'll send an HTTP POST payload to the webhook's configured URL.

Webhooks can be created on a program level and are bound to the permissions of the user that creates the webhook. Once configured, the webhook will be triggered each time one or more subscribed events occur.

Events

When configuring a webhook, you can choose which events you'd like to receive payloads for. Each event corresponds to a certain set of actions that can happen to your program and reports. It's best to subscribe to the specific events you plan to handle as it'll limit the number of HTTP requests to your server. For example, if you subscribe to the report_created event, you'll then receive an HTTP request with a detailed payload every time a hacker submits a new vulnerability report to your program.

These are the available events:

Event Description
report_agreed_on_going_public Triggers when a report is agreed to be publicly disclosed.
report_bounty_awarded Triggers when a hacker is awarded a bounty as a result of their report.
report_bounty_suggested Triggers when a bounty is suggested against a report.
report_closed_as_duplicate Triggers when a report is closed as a duplicate of another report.
report_closed_as_informative Triggers when a report is closed as informative.
report_closed_as_spam Triggers when a report is closed as spam.
report_closed_as_not_applicable Triggers when a report is closed as not applicable.
report_comment_created Triggers each time a comment is created on a report.
report_comments_closed Triggers when a report is locked.
report_created Triggers each time a new vulnerability report is created.
report_custom_field_value_updated Triggers when a report custom field value is updated.
report_needs_more_info Triggers when a report is requiring new information.
report_new Triggers when a report has changed which requires program input.
report_reopened Triggers when a report is reopened.
report_resolved Triggers when a report is resolved.
report_retest_approved Triggers when report retest results are approved.
report_retest_rejected Triggers when report retest results are rejected.
report_retest_user_completed Triggers when report retest is completed by a hacker.
report_retest_user_expired Triggers when report retest user was removed from retest due to a timeout.
report_retest_user_left Triggers when report retest user left retest before completing.
report_retesting Triggers when report changed to retesting.
report_triaged Triggers when a report is triaged.
report_group_assigned Triggers when a report is assigned to another group.
report_manually_disclosed Triggers when a report is manually disclosed.
report_not_eligible_for_bounty Triggers when a report is marked as not eligible for bounty.
report_became_public Triggers when a report is made publicly accessible.
report_undisclosed Triggers when a report is undisclosed.
report_swag_awarded Triggers when a hacker is rewarded swag as a result of their report.
report_user_assigned Triggers when a user is assigned to a report.
program_hacker_joined Applies only to private programs. Triggers each time a hacker joins your program.
program_hacker_left Applies only to private programs. Triggers each time a hacker leaves your program.

Payloads

Each webhook event has a predefined payload format with the relevant event information. Each payload contains the relevant report information and information about the reporter.

Delivery headers

Every HTTP request made to your webhooks' configured URL contains these special headers:

Header
Description
X-H1-Event The name of the event that triggered the delivery.
X-H1-Delivery A GUID to uniquely identify the delivery.
X-H1-Signature The HMAC hexdigest of the request body.

If a secret is specified, the HMAC hexdigest is generated based on the request body using the SHA256 hash function and the secret as the HMAC key.
If no secret is specified, the HMAC hexdigest is solely generated based on the request body using the SHA256 hash function.

See the validating payloads section for more info.

Example delivery

{
  "data": {
    "activity": {
      "type": "activity-bug-filed",
      "id": "1337",
      "attributes": {
        "message": "",
        "created_at": "2020-02-25T08:05:21.674Z",
        "updated_at": "2020-02-25T08:05:21.674Z",
        "internal": false
      },
      "relationships": {
        "actor": {
          "data": {
            "id": "1",
            "type": "user",
            "attributes": {
              "username": "hacker",
              "name": "Hacker One",
              "disabled": false,
              "created_at": "2019-08-01T13:53:04.239Z",
              "profile_picture": {
                "62x62": "/assets/avatars/default.png",
                "82x82": "/assets/avatars/default.png",
                "110x110": "/assets/avatars/default.png",
                "260x260": "/assets/avatars/default.png"
              },
              "signal": null,
              "impact": null,
              "reputation": 107,
              "bio": null,
              "website": null,
              "location": null,
              "hackerone_triager": null
            }
          }
        }
      }
    },
    "report": {
      "id": "548",
      "type": "report",
      "attributes": {
        "title": "Critical vulnerability!",
        "state": "new",
        "created_at": "2020-02-25T08:05:21.405Z",
        "vulnerability_information": "## Summary:\n[add summary of the vulnerability]\n\n## Steps To Reproduce:\n[add details for how we can reproduce the issue]\n\n  1. [add step]\n  1. [add step]\n  1. [add step]\n\n## Supporting Material/References:\n[list any additional material (e.g. screenshots, logs, etc.)]\n\n  * [attachment / reference]\n\n## Impact\n\nThe trouble I've seen",
        "triaged_at": null,
        "closed_at": null,
        "last_reporter_activity_at": "2020-02-25T08:05:21.674Z",
        "first_program_activity_at": "2020-02-25T08:05:21.674Z",
        "last_program_activity_at": "2020-02-25T08:05:21.674Z",
        "bounty_awarded_at": null,
        "swag_awarded_at": null,
        "disclosed_at": null,
        "reporter_agreed_on_going_public_at": null,
        "last_public_activity_at": "2020-02-25T08:05:21.674Z",
        "last_activity_at": "2020-02-25T08:11:20.699Z",
        "source": null
      },
      "relationships": {
        "reporter": {
          "data": {
            "id": "1",
            "type": "user",
            "attributes": {
              "username": "hacker",
              "name": "HackerOne",
              "disabled": false,
              "created_at": "2019-08-01T13:53:04.239Z",
              "profile_picture": {
                "62x62": "/assets/avatars/default.png",
                "82x82": "/assets/avatars/default.png",
                "110x110": "/assets/avatars/default.png",
                "260x260": "/assets/avatars/default.png"
              },
              "signal": null,
              "impact": null,
              "reputation": null,
              "bio": null,
              "website": "http://hackerone.com",
              "location": null,
              "hackerone_triager": null
            }
          }
        },
        "severity": {
          "data": {
            "id": "1",
            "type": "severity",
            "attributes": {
              "rating": "low",
              "author_type": "User",
              "user_id": "1",
              "created_at": "2020-02-25T08:05:21.405Z"
            }
          }
        }
      }
    }
  }
}

Validating payloads from HackerOne

To validate that a request originated from HackerOne, we suggest that you provide a secret for all webhooks set up on our platform.

This secret is used to generate the X-H1-Signature header containing the HMAC hexdigest of the request body signed with the provided secret as the key. If the secret is not provided, the signature is generated with an empty string key ('').

To verify the validity of any given request, the goal is to generate a hash of the body on your end-service using the shared secret, and validate this against the given X-H1-SIGNATURE header. If a simple endpoint is listening to our incoming webhooks, our requests can be verified by hash validation against our signature as shown in these implementation examples:


Python:

Assuming that the request is a request with an accessible JSON format body attribute:

import json
import hmac

def validate_request(request, secret, signature):
    [ _, digest ] = signature.split('=')

    generated_digest = hmac.new(secret.encode(), request['body'].encode(), "sha256").hexdigest()

    return hmac.compare_digest(digest, generated_digest)

Ruby:

A similar implementation in Ruby (making use of Racks secure_compare constant time comparator):

def validate_request(body, secret, signature)
  _, digest = signature.split("=")
  generated_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, body)
  return halt 500, "Invalid signature!" unless Rack::Utils.secure_compare(generated_digest, digest)
end

Javascript:

A similar implementation in Javascript using Node.js HTTP server

var http = require('http');
var crypto = require('crypto');

const secret = "mysecret"

http.createServer(function (req, res) {
    let sig = req.headers['x-h1-signature'].split('=')[1]
    let data = []
    let valid_signature = false
    req.on('data', chunk => {
        data.push(chunk)
    })
    req.on('end', () => {
        const hash = crypto.createHmac('sha256', secret).update(data[0]).digest('hex')

        valid_signature = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(hash));

        res.writeHead(200, {'Content-Type': 'text/plain'});
        let json_hash = {
            "signature_valid": valid_signature          
        }
        res.end(JSON.stringify(json_hash))
    })  
}).listen(7777)

Although implementations may differ, there are two key points to keep in mind regarding implementations: