# Webhooks & Events

> Set up webhooks to react to Encore events


Webhooks provide a way for notifications to be delivered to an HTTP endpoint of your choice whenever certain events happen within Encore.
For example, you can set up a webhook to be notified whenever a deployment starts or finishes.

Webhooks are defined on a per-application basis, and are configured under Settings -> Webhooks in the [Encore Cloud dashboard](https://app.encore.cloud).

To simplify using webhooks, Encore.go provides a Go module, [go.encore.dev/webhooks](https://pkg.go.dev/go.encore.dev/webhooks), that provides
type definitions and documentation of all supported webhook events. This module is kept up to date as new events are added.

## Webhook Deliveries

Each time an event occurs that matches one of your defined webhooks,
Encore will send a HTTP POST request to the webhook's configured URL with information about the event.

If the HTTP request fails, the delivery is marked as failed and won't be retried.

Each event is given a unique event id, which is shared across all webhooks.

Within each webhook, each event is given a sequence number, which is incremented for each event
that matches that webhook. The sequence number allows for a linear ordering of events within a webhook,
making it easy to determine if an event was missed.

These are provided in the `X-Encore-Event-Id` and `X-Encore-Sequence-Id` headers respectively,
and are also part of the event payload itself.

## Parsing webhook events

To parse a webhook event, use the [`webhooks.ParseEvent`](https://pkg.go.dev/go.encore.dev/webhooks#ParseEvent) function.

As you'll see in the example below, to parse the webhook event you'll need access to the webhook secret.
This is a secret value that is generated by Encore and is used to sign each webhook request. More about this
in the next section.

For example, to process rollout started and completed webhook events,
you could do something like this:

```go
package service

import (
    "net/http"

    "go.encore.dev/webhooks"
)

var secrets struct {
	EncoreWebhookSecret string
}

//encore:api public raw
func Webhook(w http.ResponseWriter, req *http.Request) {
	payload, err := io.ReadAll(req.Body)
	if err != nil {
		// ... handle error
    }
    event, err := webhooks.ParseEvent(payload, req.Header.Get("X-Encore-Signature"), secrets.EncoreWebhookSecret)
    if err != nil {
        // ... handle error
    }

    switch data := event.Data.(type) {
    case *webhooks.RolloutCreatedEvent:
        // ... handle rollout created event
    case *webhooks.RolloutCompletedEvent:
        // ... handle rollout completed event
    }
}
```

<Callout type="info">

Note that the example above is written as an Encore API endpoint, but that's not required.
The same code works in any Go HTTP server, and the `go.encore.dev/webhooks` library does not depend on
any Encore-specific functionality.

</Callout>

## Checking webhook signatures

Since the webhook endpoint is publicly accessible, it is important to validate that the request is coming from Encore.
To do so, Encore generates a secret for each webhook, which is used to sign each request.

The webhook secret can be found on the webhook details page by admins.

If you use the [go.encore.dev/webhooks](https://pkg.go.dev/go.encore.dev/webhooks) library then signature validation
is handled automatically, but it's also possible to verify the signature manually (see below).

### Preventing replay attacks

A [replay attack](https://en.wikipedia.org/wiki/Replay_attack) occurs when an attacker intercepts a valid request,
including the payload and signature, and re-transmits it one or more times, causing unintended side effects.

To mitigate such attacks, Encore includes a timestamp in the `X-Encore-Signature` header.
This timestamp is part of the signed payload, which means that it can't be changed by the attacker
without invalidating the signature. This makes it possible to mitigate replay attacks by ensuring the
timestamp isn't older than a certain threshold (the `go.encore.dev/webhooks` library defaults to 5 minutes).

### Verifying signatures manually

The `X-Encore-Signature` header included in each webhook event contains a timestamp and one or more *schemes*.
The timestamp is prefixed with `t=`, and each *scheme* is prefixed by a `v` and a version number.
Currently only the `v1` *scheme* is supported.

For example, a valid signature header might look like this:
```
X-Encore-Signature: t=1623345600,v1=0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b
```

The `v1` scheme is using a hash-based message authentication code ([HMAC](https://en.wikipedia.org/wiki/HMAC))
with [SHA-256](https://en.wikipedia.org/wiki/SHA-2). To prevent downgrade attacks, ignore all schemes that are not `v1`.

It's possible the signature contains multiple signatures, for example if the webhook secret has been rotated recently.

When rotating the webhook secret, Encore lets you define for how long the old secret should continue to be valid for.
During that window, each webhook event will be signed with the new and the old secret.

To validate the webhook signature, follow the algorithm below:

**Step 1: Extract the timestamp and signatures from the header**
Split the header on `,` to get a list of fields, then split each field on `=` to get the key and value.

The value of the `t` key is the timestamp, and represents the UNIX timestamp (in seconds) when the signature was created.
The fields with the `v1` key (possibly several, in case of secret rotation) are the signatures.

Discard any other fields.

**Step 2: Prepare the payload to sign**
Create the payload to sign by concatenating the timestamp (as a string) and the request body, separated by `.` like so:

```go
payloadToSign := timestamp + "." + string(payload)
```

**Step 3: Compute the expected signature**
Compute the HMAC with the SHA256 hash function, using the webhook secret as the key and the `payloadToSign` as the message.

Then, encode the resulting HMAC using the base64 URL encoding, and trim any trailing `=` characters.
In Go, this can be done like so:

```go
h := hmac.New(sha256.New, []byte(webhookSecret))
h.Write([]byte(payloadToSign))
digest := h.Sum(nil)
expectedSignature := base64.RawURLEncoding.EncodeToString(digest)
```

**Step 4: Compare the signatures**
Compare each signature with the `v1` field in the header with the expected signature.
To protect against timing attacks, use a constant-time comparison function (like `crypto/hmac.Equal` in Go).

If none of the signatures match, reject the request.

If a match is found, compare the timestamp with the current time. If the difference is greater
than the allowed threshold (5 minutes is a reasonable default), reject the request.

Otherwise, accept the request.
