# Webhook Integration Guide

### Purpose

This webhook is triggered **each time a new video recording is received** in your Birdie workspace. It allows you to automate workflows, store references, or trigger actions when a video becomes available.

***

### Webhook Delivery

* **Method:** `POST`
* **Protocol:** `HTTPS` only
* **Content-Type:** `application/json`

Birdie sends a signed POST request to the endpoint URL you register. The JSON body contains data about the received video.

***

### Get Started

Head to your Birdie workspace settings page and enable [Receive recordings via a Webhook](https://app.birdie.so/settings#forward). \
You must validate your URL endpoint by returning a [valid response](#webhook-response-requirements) before saving.\
From there copy your Singing Secret to setup [Signature Verification](#signature-verification).&#x20;

***

### Signature Verification

To ensure the webhook is authentic, each request includes a `Signature` HTTP header. This is a HMAC-SHA256 hash of the JSON payload using your Signing Secret.

```http
Signature: <HMAC_SHA256_SIGNATURE>
```

#### Hash generation formula:

```
HMAC_SHA256(<payload>, your_signing_secret)
```

#### Verifying Signature - Code Examples

Hash the Json Payload with your Signing Secret, and make sure it match the `Signature` Header. &#x20;

{% tabs %}
{% tab title="Node.js (Express)" %}

```js
const crypto = require('crypto');

app.post('/webhook', express.json(), (req, res) => {
  const signature = req.header('Signature');
  const payload = JSON.stringify(req.body);
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }

  res.send('ok');
});
```

{% endtab %}

{% tab title="PHP (Laravel) " %}

```php
$payloadJson = file_get_contents('php://input');
$signature = hash_hmac('sha256', $payloadJson, '<your_secret>');
$providedSignature = $request->header('Signature');

if ($signature !== $providedSignature) {
    return response('Invalid signature', 401);
}

return response('ok', 200);
```

{% endtab %}

{% tab title="Python (Flask)" %}

```python
import hmac, hashlib
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    secret = b'your_secret_here'
    payload = request.get_data()
    provided = request.headers.get('Signature')
    expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()

    if provided != expected:
        return 'Invalid signature', 401

    return 'ok', 200
```

{% endtab %}

{% tab title="Ruby (Sinatra)" %}

```ruby
require 'sinatra'
require 'openssl'

post '/webhook' do
  payload = request.body.read
  signature = request.env['HTTP_SIGNATURE']
  expected = OpenSSL::HMAC.hexdigest('sha256', ENV['WEBHOOK_SECRET'], payload)

  halt 401, 'Invalid signature' unless Rack::Utils.secure_compare(expected, signature)
  'ok'
end
```

{% endtab %}

{% tab title="Go" %}

```go
import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "io/ioutil"
  "net/http"
)

func webhookHandler(w http.ResponseWriter, r *http.Request) {
  body, _ := ioutil.ReadAll(r.Body)
  signature := r.Header.Get("Signature")

  mac := hmac.New(sha256.New, []byte("your_secret"))
  mac.Write(body)
  expected := hex.EncodeToString(mac.Sum(nil))

  if signature != expected {
    http.Error(w, "Invalid signature", http.StatusUnauthorized)
    return
  }

  w.Write([]byte("ok"))
}
```

{% endtab %}

{% tab title=".NET (ASP.NET Core)" %}

```csharp
[HttpPost("webhook")]
public IActionResult Webhook([FromBody] JObject body) {
    var secret = Encoding.UTF8.GetBytes("your_secret");
    var payload = Request.Body;

    using var reader = new StreamReader(payload);
    var bodyStr = reader.ReadToEnd();
    var hmac = new HMACSHA256(secret);
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(bodyStr));
    var expected = BitConverter.ToString(hash).Replace("-", "").ToLower();

    var signature = Request.Headers["Signature"].ToString();
    if (expected != signature) return Unauthorized("Invalid signature");

    return Ok("ok");
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
For additional security layer you can also verify the IP address from Birdie servers: \
If you want to go this way, allow calls coming from `18.189.92.93` and `3.20.198.186`
{% endhint %}

***

### Webhook Response Requirements

Your webhook handler must:

* Return an HTTP status code **between 200 and 299** to confirm successful delivery.
* Avoid redirects, errors, or timeouts — otherwise the attempt is marked as failed.

***

### Retry Policy & Backoff

Birdie uses exponential backoff for failed webhook deliveries. Each webhook will be retried up to **3 times**, as follows:

1. **1st attempt:** immediately
2. **2nd attempt:** 10 seconds later
3. **3rd attempt:** 100 seconds later

If all attempts fail, the webhook is marked as permanently failed.

***

### Auto-Disabling Policy

To prevent loops or spam, Birdie disables your webhook endpoint **automatically** after **10 consecutive failures**. You will be able to re-enable the webhook in your Birdie dashboard.

***

### Need Help?

You can use any webhook testing service of your choice to test and introspect Birdie calls, for example <https://webhook.site>

If needed, email us at <support@birdie.so> we’ll help you resolve it quickly.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.birdie.so/birdie-docs/birdie-api/webhook-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
