Skip to main content

Custom webhook

Custom webhooks allow you to receive GitGuardian notifications on any server that accepts incoming json-encoded HTTP "POST" requests.

We use HMAC with sha256 as a hash function to sign the payload of our requests. The key used is a string concatenation of the timestamp and the signature token. This allows you to check that requests are coming from GitGuardian and that the payload was not altered during transport. See below how to implement the verification procedure. You can set the signature token in your settings.

The “Timestamp” field in the header counters replay attacks. If your current timestamp differs from our sending “Timestamp” by more than a few seconds, it is safer to drop the request.

A custom header can also be added to the requests from your settings to specify, for example, the environment or service.

How to integrate#

  1. Navigate to Settings > Workspace > Integrations > Custom webhook

  2. Create a new custom webhook with the name of your webhook and the URL where you want to receive GitGuardian notifications.

    We propose a default signature token which you can edit in order to verify that the post request comes from GitGuardian. Make sure to store the signature token in a safe place as you won't be able to access it again. Lastly, choose when you want to receive notifications.

    Custom webhook form

  3. Configure the endpoint on your side to verify the request and handle GitGuardian alerts (see details below)

Payloads#

The payload is described by the following openAPI structure:

scan_result:  type: object  title: Scan Result  description: Result of a policy evaluation.  required:    - author    - origin    - date    - type    - policy    - gitguardian_link    - break_url    - matches  properties:    author:      type: object      title: Author      properties:        info:          type: string          description: author contact        name:          type: string          description: author name    origin:      type: string      description: origin of message    date:      type: string      description: date of detection    type:      type: string      description: incident type    policy:      type: string      description: policy responsible for detection    gitguardian_link:      type: string      description: url of incident on the dashboard.    break_url:      type: string      description: url of found incident.    matches:      type: array      description: list of incidents matches.      minItems: 0      items:      $ref: '#/components/schemas/match'match:  type: object  required:    - type    - match  properties:    type:      type: string      description: Type of match    match:      type: string      description: Matched string of failure. If it's a secret it won't correspond to the real match.    pre_line_start:      type: integer      description: start line number of match (index origin = 1) in previous patch.    pre_line_end:      type: integer      description: end line number of match (index origin = 1) in previous patch.    post_line_start:      type: integer      description: start line number of match (index origin = 1) in post patch.    post_line_end:      type: integer      description: end line number of match (index origin = 1) in post patch.    index_start:      type: integer      description: start index of match in file as an array (index origin = 0)    index_end:      type: integer      description: end index of match in file as an array (index origin = 0)

You may send a test message in order to check that everything works, here is a sample payload of the test message you will receive:

{  "author": {    "info": "sample@sample.sample",    "name": "Sample Sample"  },  "origin": "GitGuardian",  "date": "2042-10-10 04:00:00 PM",  "type": "Welcome Message Token",  "policy": "Secrets detection",  "gitguardian_link": "http://dashboard.gitguardian.com/workspace/34123/incidents/8213",  "break_url": "github.com/sample_user/sample_repo/compare#ae32df",  "matches": [    {      "type": "client_id",      "match": "--censored--",      "index_start": 74,      "index_end": 86,      "pre_line_start": 2,      "pre_line_end": 2,      "post_line_start": 3,      "post_line_end": 3    },    {      "type": "client_secret",      "match": "--censored--",      "index_start": 30,      "index_end": 44,      "pre_line_start": 1,      "pre_line_end": 1,      "post_line_start": 2,      "post_line_end": 2    }  ]}

Verifying the payload signature#

Here is a python3 example of how to verify the signature of our notifications:

  • signature is from our request's headers
  • timestamp is from our request's headers
  • signature_token is the token that you have specified in the settings
  • payload is our request body, decoded from “utf-8” (i.e. string representing our json)
import hmacimport hashlib
def verify_signature(signature: str, timestamp: str, signature_token: str, payload: str) -> bool:  if not signature.startswith("sha256="):    return False
  signature = signature.split("sha256=")[-1]  hmac_digest = hmac.new(key=bytes(timestamp + signature_token, "utf-8"),                         msg=bytes(payload, "utf-8"),                         digestmod=hashlib.sha256).hexdigest()  return hmac.compare_digest(signature, hmac_digest)

Here is a very basic unit test that lets you check that your implementation is correct:

  assert verify_signature_gitguardian(    signature="sha256=172fe3d694b734aa53dc892fd3b8d62163fc240064de570ba006900bb54a0fc2",    timestamp="0",    signature_token="foo",    payload="bar"  )

Using AWS Lambda#

When using AWS lambda, a HTTP API Gateway must be used as trigger.

HTTP API Gateway

Below are two examples, respectively in Javascript and Python, on how to validate the payload received from the dashboard.

Javascript#

const { createHmac } = require('crypto')
function verifySignature(signature, timestamp, signatureToken, payload) {  var signatureHeader = signature.substring(0, 7)  if (signatureHeader !== 'sha256=') {    return false  }
  var signatureActual = signature.split('=')[1]
  var hmac = createHmac(    'sha256',    Buffer.from(timestamp + signatureToken, 'utf8')  )  hmac.update(payload)
  var result = hmac.digest('hex')
  if (result === signatureActual) {    return true  } else {    return false  }}
exports.handler = async (event) => {  const payload_signature = event.headers['x-gitguardian-signature']  const timestamp = event.headers['timestamp']  const webhook_token = '<INSERT SIGNATURE TOKEN HERE>'  const payload = event.body
  let statusCode  if (verifySignature(payload_signature, timestamp, webhook_token, payload)) {    console.log('OK')    statusCode = 200  } else {    statusCode = 400    throw new Error()  }  const response = {    statusCode,  }  return response}

Python#

import hmacimport hashlibimport json

def verify_signature(signature: str, timestamp: str, signature_token: str, payload: str) -> bool:    if not signature.startswith("sha256="):        return False    signature = signature.split("sha256=")[-1]    hmac_digest = hmac.new(key=bytes(timestamp + signature_token, "utf-8"),                           msg=bytes(payload, "utf-8"),                           digestmod=hashlib.sha256).hexdigest()    return hmac.compare_digest(signature, hmac_digest)

def lambda_handler(event, context):    payload_dump = event["body"]
    timestamp = event["headers"]["timestamp"]    payload_signature = event["headers"]["x-gitguardian-signature"]    webhook_token = "<INSERT SIGNATURE TOKEN HERE>"
    if verify_signature(payload_signature, timestamp, webhook_token, payload_dump):        return {'statusCode': 200, 'body': json.dumps('Success') }
    return {'statusCode': 400, 'body': json.dumps('Failed to verify') }