> For the complete documentation index, see [llms.txt](https://docs.hypertune.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hypertune.com/integrations/webhooks/handling-webhooks.md).

# Handling webhooks

This page explains how webhook events from Hypertune should be handled by your server.

## Request structure

When an event occurs in your project, we'll notify all the subscribed active webhooks.

We do this by sending them each a POST request to their payload URLs. This request has a body that contains information about the event, for example:

```json
{
  "id": "QAq7JB7nE5tY8mS99qsmM",
  "createdAt": "2023-04-17T12:02:26.095Z",
  "type": "NEW_COMMIT",
  "project": {
    "id": "2489",
    "name": "demo-webhook project"
  },
  "commit": {
    "id": "2792",
    "message": "v5"
  },
  "user": {
    "id": "2431",
    "displayName": "Miraan Tabrez",
    "email": "miraan@hypertune.com"
  }
}
```

Additionally, it will include the `X-Hypertune-Signature` header which will be the HMAC-SHA-256 digest in hexadecimal format of the raw body, using the webhook secret.

We will retry webhook sends with exponential backoff until a limit, so your implementation should be idempotent and handle missing hooks.

## Processing webhooks

To process a request you should:

1. Verify it is from Hypertune. To do this, validate that the `X-Hypertune-Signature` matches the definition above, using the secret you configured when setting up your webhook.
2. Within 10 seconds, respond with a successful status code: one in the range 200-299 inclusive.

## JavaScript example

```typescript
import { createHmac, timingSafeEqual } from 'node:crypto'
import express from 'express'

// Do not hardcode in a production application
const WEBHOOK_SECRET = '1de87274cc2ddba774cbcec5a9ad8727'

const app = express()

// If you want to use body parser JSON, do something like this:
// https://github.com/stripe/stripe-node/issues/341#issuecomment-304733080
app.use(express.raw({ type: '*/*' }))

app.post('/', (req, res) => {
  const actualSignature = [
    req.headers['x-hypertune-signature'],
  ].flat()[0]
  if (!actualSignature) {
    console.log(
      'Rejected webhook request with missing signature'
    )
    res.status(401).send('Missing X-Hypertune-Signature header')
    return
  }

  const expectedSignature = createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body.toString())
    .digest('hex')
  if (
    actualSignature.length !== expectedSignature.length ||
    !timingSafeEqual(
      Buffer.from(actualSignature),
      Buffer.from(expectedSignature)
    )
  ) {
    console.log('Rejected webhook request with bad signature')
    res.status(403).send('Bad signature')
    return
  }

  console.log('Received valid webhook event')
  const data = JSON.parse(req.body.toString())
  // TODO: Do something interesting with the data here

  res.sendStatus(204)
})

const port = 8080

app.listen(port)
console.log(`Server listening at http://localhost:${port}`)
```
