> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reap.video/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications when your video projects finish processing

> **For AI agents:** a documentation index is at [/llms.txt](/llms.txt). Every page is also available as markdown, just append `.md` to the URL.

## Overview

Webhooks let Reap push status updates to your server the moment a video project reaches a final state -- no more polling. When a project completes, fails, or expires, Reap sends a POST request to your configured endpoint with the project's status.

## How It Works

<Steps>
  <Step title="Create a Project">
    Use the API to create a clipping, captions, transcription, reframe, or dubbing project as usual.
  </Step>

  <Step title="Processing Begins">
    Reap processes your video in the background.
  </Step>

  <Step title="Project Reaches Final State">
    The project finishes with a status of `completed` or `invalid`.
  </Step>

  <Step title="Reap Sends a Webhook">
    A POST request with the project status is sent to every active webhook URL configured in your studio.
  </Step>

  <Step title="Your Server Processes the Event">
    Your endpoint receives the payload, returns HTTP 200 with an empty body, and handles the event.
  </Step>
</Steps>

## Webhook Payload

Every webhook delivery sends a JSON POST request with this structure:

```json theme={"system"}
{
  "projectId": "65f1a2b3c4d5e6f7a8b9c0d1",
  "projectType": "clipping",
  "source": "Upload",
  "status": "completed"
}
```

<ResponseField name="projectId" type="string">
  Unique identifier of the project.
</ResponseField>

<ResponseField name="projectType" type="string">
  Type of project: `clipping`, `captions`, `reframe`, `dubbing`, or `transcription`.
</ResponseField>

<ResponseField name="source" type="string">
  Source of the original video: `Youtube`, `Upload`, or `Generic`.
</ResponseField>

<ResponseField name="status" type="string">
  The final status that triggered the webhook.
</ResponseField>

### Statuses That Trigger Webhooks

| Status      | Description                                                        |
| ----------- | ------------------------------------------------------------------ |
| `completed` | Project finished processing successfully. Clips/results are ready. |
| `invalid`   | The source video was invalid or processing failed.                 |
| `expired`   | Project results expired and are no longer available.               |

<Note>
  Webhooks are only sent when a project reaches a **final state**. You will not receive webhooks for intermediate states like `processing`.
</Note>

## Setting Up a Webhook

Webhooks are configured from the Reap dashboard, not via the API.

<Note>
  The documentation is public. Dashboard links require a Reap account and are only needed for account setup, API key management, integrations, or webhook configuration.
</Note>

<Steps>
  <Step title="Open Webhook Settings">
    Log in to your [Reap dashboard](https://app.reap.video) and navigate to **Profile** > **Settings** > **Webhooks**.
  </Step>

  <Step title="Create a New Webhook">
    Click **Create Webhook**. Provide a descriptive name and your HTTPS endpoint URL.
  </Step>

  <Step title="Validation">
    Reap sends a test POST request to your URL with the following dummy payload. Your endpoint must return HTTP **200** with an **empty response body** within **5 seconds**.

    ```json theme={"system"}
    {
      "projectId": "000000000000000000000000",
      "projectType": "clipping",
      "source": "Upload",
      "status": "completed"
    }
    ```
  </Step>

  <Step title="Webhook is Active">
    Once the test passes, the webhook is saved and active. All future project status changes in your studio will be delivered to this endpoint.
  </Step>
</Steps>

<Warning>
  Your endpoint must be live and responding correctly **before** you create the webhook. Reap will reject the webhook if the test request fails.
</Warning>

## Endpoint Requirements

Your webhook endpoint must meet these requirements:

<CardGroup cols={2}>
  <Card title="HTTPS Only" icon="lock">
    The URL must use HTTPS. HTTP, localhost, and private IP addresses are not allowed.
  </Card>

  <Card title="Accept POST with JSON" icon="code">
    Your endpoint must accept POST requests with a `Content-Type: application/json` body.
  </Card>

  <Card title="Return 200 Empty" icon="check">
    Respond with HTTP status **200** and an **empty response body**. Any other status code is treated as a failure.
  </Card>

  <Card title="Respond Within Timeout" icon="clock">
    Respond within **5 seconds** during webhook creation/validation, and within **10 seconds** for live deliveries. Exceeding the timeout counts as a failure.
  </Card>
</CardGroup>

### URL Restrictions

Your webhook URL must pass these validation checks:

* Must use the `https://` scheme
* Cannot point to `localhost`, `127.0.0.1`, or `0.0.0.0`
* Cannot point to private or reserved IP address ranges

## Example Webhook Receiver

<CodeGroup>
  ```javascript Node.js (Express) theme={"system"}
  const express = require('express');
  const app = express();

  app.use(express.json());

  app.post('/webhook/reap', (req, res) => {
    const { projectId, projectType, source, status } = req.body;

    // Return 200 immediately with empty body
    res.status(200).send('');

    // Process the event asynchronously
    handleProjectUpdate(projectId, projectType, source, status);
  });

  async function handleProjectUpdate(projectId, projectType, source, status) {
    if (status === 'completed') {
      // Fetch clips or project details from the API
      console.log(`Project ${projectId} completed. Fetching results...`);
    } else if (status === 'invalid') {
      console.log(`Project ${projectId} failed with status: ${status}`);
    } else if (status === 'expired') {
      console.log(`Project ${projectId} expired.`);
    }
  }

  app.listen(3000, () => console.log('Webhook receiver running on port 3000'));
  ```

  ```python Python (Flask) theme={"system"}
  from flask import Flask, request

  app = Flask(__name__)

  @app.route('/webhook/reap', methods=['POST'])
  def handle_webhook():
      data = request.json
      project_id = data['projectId']
      status = data['status']

      # Process asynchronously in production (e.g. Celery task)
      if status == 'completed':
          print(f'Project {project_id} completed. Fetching results...')
      elif status == 'invalid':
          print(f'Project {project_id} failed with status: {status}')
      elif status == 'expired':
          print(f'Project {project_id} expired.')

      # Return 200 with empty body
      return '', 200

  if __name__ == '__main__':
      app.run(port=3000)
  ```

  ```go Go theme={"system"}
  package main

  import (
  	"encoding/json"
  	"fmt"
  	"net/http"
  )

  type WebhookPayload struct {
  	ProjectID   string `json:"projectId"`
  	ProjectType string `json:"projectType"`
  	Source      string `json:"source"`
  	Status      string `json:"status"`
  }

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	var payload WebhookPayload
  	json.NewDecoder(r.Body).Decode(&payload)

  	// Return 200 with empty body immediately
  	w.WriteHeader(http.StatusOK)

  	// Process the event
  	go func() {
  		if payload.Status == "completed" {
  			fmt.Printf("Project %s completed. Fetching results...\n", payload.ProjectID)
  		}
  	}()
  }

  func main() {
  	http.HandleFunc("/webhook/reap", webhookHandler)
  	fmt.Println("Webhook receiver running on port 3000")
  	http.ListenAndServe(":3000", nil)
  }
  ```
</CodeGroup>

## Managing Webhooks

All webhook management is done from the [Reap dashboard](https://app.reap.video) under **Profile** > **Settings** > **Webhooks**.

You can:

* **Update** the name or URL of an existing webhook. Changing the URL triggers a new test request.
* **Disable/Enable** a webhook. Re-enabling a disabled webhook triggers a test request to verify your endpoint is working.
* **Delete** a webhook to stop receiving notifications permanently.
* **View delivery history** for each webhook, including response codes and delivery times.

<Tip>
  All active webhooks in a studio receive notifications for **every** project in that studio. You cannot scope a webhook to specific project types.
</Tip>

## Auto-Disable on Failures

Reap tracks consecutive delivery failures for each webhook:

* Each failed delivery (non-200 response, timeout, or unreachable endpoint) increments the failure counter.
* After **5 consecutive failures**, the webhook is **automatically disabled** and you are notified via email.
* Any **successful delivery** resets the failure counter back to 0.

To re-enable a disabled webhook:

1. Fix the issue with your endpoint
2. Go to **Profile** > **Settings** > **Webhooks** in the dashboard
3. Toggle the webhook back on (this triggers a test request)

<Warning>
  There are no automatic retries. If a delivery fails, Reap logs the failure and moves on. Design your system to handle missed events by periodically checking [Get Project Status](/api-reference/get-project-status) for critical projects.
</Warning>

## Plan Limits

| Plan    | Max Active Webhooks   |
| ------- | --------------------- |
| Free    | 0 (no webhook access) |
| Creator | 1                     |
| Studio  | 5                     |

<Tip>
  Need more webhooks? [Contact us](mailto:hello@reap.video) to discuss enterprise options.
</Tip>

## Best Practices

<CardGroup cols={2}>
  <Card title="Respond Fast" icon="bolt">
    Return 200 immediately and process the event asynchronously. Don't do heavy work before responding.
  </Card>

  <Card title="Handle Duplicates" icon="copy">
    Design your handler to be idempotent. In rare cases, you may receive the same event more than once.
  </Card>

  <Card title="Monitor Deliveries" icon="chart-line">
    Check webhook delivery history in your dashboard regularly to catch issues before the auto-disable threshold.
  </Card>

  <Card title="Have a Fallback" icon="rotate">
    Use [Get Project Status](/api-reference/get-project-status) as a fallback for critical workflows in case a webhook delivery is missed.
  </Card>
</CardGroup>

## Next Steps

* [Get Project Status](/api-reference/get-project-status) -- fallback polling endpoint
* [Get Project Details](/api-reference/get-project-details) -- fetch full project data after a `completed` webhook
* [Get Project Clips](/api-reference/get-project-clips) -- retrieve generated clips after completion
