Set up webhooks for real-time notifications on jobs and uploads. Learn signature verification, event handling, and local testing.
Webhooks and notifications are a powerful tool that enables event-driven behavior. Rather than looping to retrieve an object state your webhook can listen to notifications and trigger a workflow accordingly.
For example, instead of periodically checking the status of your jobs to know if they are completed, you can set up a webhook to receive notifications for the job.completed event and do your own process from there.
importChunkifyfrom'@chunkify/chunkify';constclient=newChunkify({projectAccessToken:'My Project Access Token',});// Create a webhook
constwebhook=awaitclient.webhooks.create({url:'https://example.com/webhook',events:['job.completed'],})
fromchunkifyimportChunkifyclient=Chunkify(project_access_token="My Project Access Token",)# Create a webhookwebhook=client.webhooks.create(url="https://example.com/webhook",events=["job.completed"],)
useChunkify\Client;$client=newClient(projectAccessToken:'My Project Access Token',);// Create a webhook
$webhook=$client->webhooks->create(url:'https://example.com/webhook',events:['job.completed'],);
import("context""github.com/chunkifydev/chunkify-go""github.com/chunkifydev/chunkify-go/option")client:=chunkify.NewClient(option.WithProjectAccessToken("My Project Access Token"),)// Create a webhook
webhookParams:=chunkify.WebhookNewParams{URL:"https://example.com/webhook",Events:[]string{"job.completed",},}webhook,err:=client.Webhooks.New(context.TODO(),webhookParams)iferr!=nil{log.Fatal(err)}
If you don't specify the events field when creating a webhook, it will receive all notifications for the project.
Events
Chunkify notifications system supports various events types.
Depending on the event type, the notification will contain different information.
By default, when creating a webhook all events are enabled. If you want your webhook to only receive specific events, you can pass the events parameter to the webhook creation.
Here an example for a webhook that will receive notifications when a job is completed or failed:
importChunkifyfrom'@chunkify/chunkify';constclient=newChunkify({projectAccessToken:'My Project Access Token',});// Create a webhook
constwebhook=awaitclient.webhooks.create({url:'https://example.com/webhook',events:['job.completed','job.failed'],});
fromchunkifyimportChunkifyclient=Chunkify(project_access_token="My Project Access Token",)# Create a webhookwebhook=client.webhooks.create(url="https://example.com/webhook",events=["job.completed","job.failed"],)
useChunkify\Client;$client=newClient(projectAccessToken:'My Project Access Token',);// Create a webhook
$webhook=$client->webhooks->create(url:'https://example.com/webhook',events:['job.completed','job.failed'],);
import("context""github.com/chunkifydev/chunkify-go""github.com/chunkifydev/chunkify-go/option")client:=chunkify.NewClient(option.WithProjectAccessToken("My Project Access Token"),)// Create a webhook
webhookParams:=chunkify.WebhookNewParams{URL:"https://example.com/webhook",Events:[]string{"job.completed","job.failed",},}webhook,err:=client.Webhooks.New(context.TODO(),webhookParams)iferr!=nil{log.Fatal(err)}
Webhook Signature and Notification Parsing
For security purposes, Chunkify follows the Standard Webhooks specification to sign webhooks. You will need to retrieve the following headers: webhook-id, webhook-timestamp, and webhook-signature, and use your secret key to verify that the notifications are coming from Chunkify.
You can find this key in the Chunkify App in your project settings under the webhooks section.
Here is a simple example of how your webhook server can handle the notifications:
// This example uses Express for simplicity
importexpressfrom'express';importtype{RequestHandler}from'express';importChunkifyfrom'@chunkify/chunkify';constapp=express();constPORT=8787;// Use raw body for signature verification
app.use(express.raw({type:'application/json'}));constwebhookHandler: RequestHandler=(req,res)=>{constheaders: Record<string,string>={'webhook-signature':req.header('webhook-signature')||'','webhook-id':req.header('webhook-id')||'','webhook-timestamp':req.header('webhook-timestamp')||'',};constclient=newChunkify({projectAccessToken:'My Project Access Token',webhookKey:'your secret key',});// Unwrap the webhook will also verify the signature using the secret key
letnotification: Chunkify.UnwrapWebhookEvent;try{notification=client.webhooks.unwrap(req.body.toString(),{headers});}catch(error){console.error('Error unwrapping webhook:',error);res.status(400).send('Error unwrapping webhook');return;}constevent=notification.event;// e.g., "upload.completed", "job.completed", "job.failed"
console.log('Webhook event received:',event);switch(event){case'job.completed':constpayload=notification.dataasChunkify.UnwrapWebhookEvent.NotificationPayloadJobCompleted;console.log(`Job ${payload.job.id} completed`);break;// Complete with other cases for other event type as needed.
default:console.log(`Unexpected event type: ${event}`);}res.status(200).send('OK');};app.post('/webhook',webhookHandler);app.listen(PORT,()=>{console.log(`Test server listening on http://localhost:${PORT}`);});
"""Webhook server using Python's built-in http.server."""fromhttp.serverimportHTTPServer,BaseHTTPRequestHandlerimportjsonfromchunkifyimportChunkifyfromchunkify.typesimportUnwrapWebhookEventPORT=8787# Initialize client with webhook key for signature verificationclient=Chunkify(webhook_key="your secret key",)classWebhookHandler(BaseHTTPRequestHandler):"""Handle webhook requests."""defdo_POST(self):# noqa: N802"""Handle POST requests to /webhook."""ifself.path!="/webhook":self.send_response(404)self.end_headers()return# Read raw bodycontent_length=int(self.headers.get("Content-Length",0))payload=self.rfile.read(content_length).decode("utf-8")# Extract webhook headersheaders={"webhook-signature":self.headers.get("webhook-signature",""),"webhook-id":self.headers.get("webhook-id",""),"webhook-timestamp":self.headers.get("webhook-timestamp",""),}# Unwrap the webhook (also verifies the signature)try:notification:UnwrapWebhookEvent=client.webhooks.unwrap(payload,headers=headers)exceptExceptionaserror:print(f"Error unwrapping webhook: {error}")self.send_response(400)self.send_header("Content-Type","application/json")self.end_headers()self.wfile.write(json.dumps({"error":"Error unwrapping webhook"}).encode())return# Get the event typeevent=notification.eventprint(f"Webhook event received: {event}")# Handle different event typesifevent=="job.completed":# notification.data will be DataNotificationPayloadJobCompleted at runtimeprint(f"Job {notification.data.job.id} completed")else:print(f"Unexpected event type: {event}")# Send success responseself.send_response(200)self.send_header("Content-Type","application/json")self.end_headers()self.wfile.write(json.dumps({"status":"OK"}).encode())deflog_message(self,format,*args):# noqa: N802"""Suppress default logging."""passif__name__=="__main__":server=HTTPServer(("localhost",PORT),WebhookHandler)print(f"Test server listening on http://localhost:{PORT}")try:server.serve_forever()exceptKeyboardInterrupt:print("\nShutting down server...")server.shutdown()
useChunkify\Client;useChunkify\Core\Exceptions\WebhookException;$client=newClient(webhookKey:'your secret key',);$body=file_get_contents('php://input')?:'';$headers=getallheaders();try{// Unwrapping the webhook also verifies its signature
$notification=$client->webhooks->unwrap($body,['webhook-signature'=>$headers['webhook-signature']??'','webhook-id'=>$headers['webhook-id']??'','webhook-timestamp'=>$headers['webhook-timestamp']??'',],);switch($notification->event){case'job.completed':$job=$notification->data->job;$filesCount=count($notification->data->files);printf("Job %s completed with %d files\n",$job->id,$filesCount);break;default:error_log("Unexpected event type: {$notification->event}");}http_response_code(200);echo'OK';}catch(WebhookException$error){error_log('Error unwrapping webhook: '.$error->getMessage());http_response_code(400);echo'Error unwrapping webhook';}
import("fmt""io""net/http""github.com/chunkifydev/chunkify-go""github.com/chunkifydev/chunkify-go/option")funchandleWebhook(whttp.ResponseWriter,r*http.Request){client:=chunkify.NewClient(option.WithWebhookKey("your secret key"),)// Read the request body
body,err:=io.ReadAll(r.Body)iferr!=nil{http.Error(w,"Error reading request body",http.StatusBadRequest)return}// Unwrap the notification payload
notification,err:=client.Webhooks.Unwrap(body,r.Header)iferr!=nil{http.Error(w,"Error unwrapping webhook",http.StatusBadRequest)return}switchnotification.Event{casechunkify.UnwrapWebhookEventEventJobCompleted:data:=notification.Data.AsUnwrapWebhookEventDataNotificationPayloadJobCompleted()fmt.Printf("Job %s completed with %d files\n",data.Job.ID,len(data.Files))default:fmt.Printf("unexpected event type: %s\n",notification.Event)}}funcmain(){// Register the webhook handler
http.HandleFunc("/webhook",handleWebhook)// Start the server
port:=":8787"fmt.Printf("Webhook server listening on http://localhost%s/webhook\n",port)iferr:=http.ListenAndServe(port,nil);err!=nil{fmt.Printf("Server failed to start: %v\n",err)}}
Testing your webhook integration locally
To test your webhook integration safely the Chunkify CLI provides a way to proxy the notifications to your local server.
This way you can test your webhook behavior locally before deploying it to production.
You can see how to do that in the CLI Guide.
# Overview
> Set up webhooks for real-time notifications on jobs and uploads. Learn signature verification, event handling, and local testing.
Webhooks and notifications are a powerful tool that enables event-driven behavior. Rather than looping to retrieve an object state your webhook can listen to notifications and trigger a workflow accordingly.
For example, instead of periodically checking the status of your jobs to know if they are completed, you can set up a webhook to receive notifications for the `job.completed` event and do your own process from there.
<codegroup>
<codeblock lang="Typescript">
```typescript
import Chunkify from '@chunkify/chunkify';
const client = new Chunkify({
projectAccessToken: 'My Project Access Token',
});
// Create a webhook
const webhook = await client.webhooks.create({
url: 'https://example.com/webhook',
events: ['job.completed'],
})
```
</codeblock>
<codeblock lang="Python">
```python
from chunkify import Chunkify
client = Chunkify(
project_access_token="My Project Access Token",
)
# Create a webhook
webhook = client.webhooks.create(
url="https://example.com/webhook",
events=["job.completed"],
)
```
</codeblock>
<codeblock lang="PHP">
```php
use Chunkify\Client;
$client = new Client(
projectAccessToken: 'My Project Access Token',
);
// Create a webhook
$webhook = $client->webhooks->create(
url: 'https://example.com/webhook',
events: ['job.completed'],
);
```
</codeblock>
<codeblock lang="Go">
```go
import (
"context"
"github.com/chunkifydev/chunkify-go"
"github.com/chunkifydev/chunkify-go/option"
)
client := chunkify.NewClient(
option.WithProjectAccessToken("My Project Access Token"),
)
// Create a webhook
webhookParams := chunkify.WebhookNewParams{
URL: "https://example.com/webhook",
Events: []string{
"job.completed",
},
}
webhook, err := client.Webhooks.New(context.TODO(), webhookParams)
if err != nil {
log.Fatal(err)
}
```
</codeblock>
</codegroup>
<note>
If you don't specify the `events` field when creating a webhook, it will receive all notifications for the project.
</note>
## Events
Chunkify notifications system supports various events types.
Depending on the event type, the notification will contain different information.
### Events List
| Events name | Description |
| ---------------------------------------------- | ----------------------------------------------------- |
| [job.completed](/docs/integration/webhooks/job-completed) | The transcoding job has been successfully completed |
| [job.failed](/docs/integration/webhooks/job-failed) | The transcoding job has encountered an error |
| [job.cancelled](/docs/integration/webhooks/job-cancelled) | The transcoding job has been cancelled |
| [upload.completed](/docs/integration/webhooks/upload-completed) | The upload of a video has been successfully completed |
| [upload.failed](/docs/integration/webhooks/upload-failed) | The upload of a video has encountered an error |
| [upload.expired](/docs/integration/webhooks/upload-expired) | The upload window has expired |
### Configuring Webhooks for Specific Events
By default, when creating a webhook all events are enabled. If you want your webhook to only receive specific events, you can pass the `events` parameter to the webhook creation.
Here an example for a webhook that will receive notifications when a job is completed or failed:
<codegroup>
<codeblock lang="Typescript">
```typescript
import Chunkify from '@chunkify/chunkify';
const client = new Chunkify({
projectAccessToken: 'My Project Access Token',
});
// Create a webhook
const webhook = await client.webhooks.create({
url: 'https://example.com/webhook',
events: ['job.completed','job.failed'],
});
```
</codeblock>
<codeblock lang="Python">
```python
from chunkify import Chunkify
client = Chunkify(
project_access_token="My Project Access Token",
)
# Create a webhook
webhook = client.webhooks.create(
url="https://example.com/webhook",
events=["job.completed","job.failed"],
)
```
</codeblock>
<codeblock lang="PHP">
```php
use Chunkify\Client;
$client = new Client(
projectAccessToken: 'My Project Access Token',
);
// Create a webhook
$webhook = $client->webhooks->create(
url: 'https://example.com/webhook',
events: ['job.completed', 'job.failed'],
);
```
</codeblock>
<codeblock lang="Go">
```go Go
import (
"context"
"github.com/chunkifydev/chunkify-go"
"github.com/chunkifydev/chunkify-go/option"
)
client := chunkify.NewClient(
option.WithProjectAccessToken("My Project Access Token"),
)
// Create a webhook
webhookParams := chunkify.WebhookNewParams{
URL: "https://example.com/webhook",
Events: []string{
"job.completed",
"job.failed",
},
}
webhook, err := client.Webhooks.New(context.TODO(), webhookParams)
if err != nil {
log.Fatal(err)
}
```
</codeblock>
</codegroup>
## Webhook Signature and Notification Parsing
For security purposes, Chunkify follows the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md) to sign webhooks. You will need to retrieve the following headers: `webhook-id`, `webhook-timestamp`, and `webhook-signature`, and use your secret key to verify that the notifications are coming from Chunkify.
You can find this key in the Chunkify App in your project settings under the webhooks section.
Here is a simple example of how your webhook server can handle the notifications:
<codegroup>
<codeblock lang="Typescript">
```typescript Typescript
// This example uses Express for simplicity
import express from 'express';
import type { RequestHandler } from 'express';
import Chunkify from '@chunkify/chunkify';
const app = express();
const PORT = 8787;
// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));
const webhookHandler: RequestHandler = (req, res) => {
const headers: Record<string, string> = {
'webhook-signature': req.header('webhook-signature') || '',
'webhook-id': req.header('webhook-id') || '',
'webhook-timestamp': req.header('webhook-timestamp') || '',
};
const client = new Chunkify({
projectAccessToken: 'My Project Access Token',
webhookKey: 'your secret key',
});
// Unwrap the webhook will also verify the signature using the secret key
let notification: Chunkify.UnwrapWebhookEvent;
try {
notification = client.webhooks.unwrap(req.body.toString(), { headers });
} catch (error) {
console.error('Error unwrapping webhook:', error);
res.status(400).send('Error unwrapping webhook');
return;
}
const event = notification.event; // e.g., "upload.completed", "job.completed", "job.failed"
console.log('Webhook event received:', event);
switch (event) {
case 'job.completed':
const payload = notification.data as Chunkify.UnwrapWebhookEvent.NotificationPayloadJobCompleted;
console.log(`Job ${payload.job.id} completed`);
break;
// Complete with other cases for other event type as needed.
default:
console.log(`Unexpected event type: ${event}`);
}
res.status(200).send('OK');
};
app.post('/webhook', webhookHandler);
app.listen(PORT, () => {
console.log(`Test server listening on http://localhost:${PORT}`);
});
```
</codeblock>
<codeblock lang="Python">
```python Python
"""Webhook server using Python's built-in http.server."""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from chunkify import Chunkify
from chunkify.types import UnwrapWebhookEvent
PORT = 8787
# Initialize client with webhook key for signature verification
client = Chunkify(
webhook_key="your secret key",
)
class WebhookHandler(BaseHTTPRequestHandler):
"""Handle webhook requests."""
def do_POST(self): # noqa: N802
"""Handle POST requests to /webhook."""
if self.path != "/webhook":
self.send_response(404)
self.end_headers()
return
# Read raw body
content_length = int(self.headers.get("Content-Length", 0))
payload = self.rfile.read(content_length).decode("utf-8")
# Extract webhook headers
headers = {
"webhook-signature": self.headers.get("webhook-signature", ""),
"webhook-id": self.headers.get("webhook-id", ""),
"webhook-timestamp": self.headers.get("webhook-timestamp", ""),
}
# Unwrap the webhook (also verifies the signature)
try:
notification: UnwrapWebhookEvent = client.webhooks.unwrap(payload, headers=headers)
except Exception as error:
print(f"Error unwrapping webhook: {error}")
self.send_response(400)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "Error unwrapping webhook"}).encode())
return
# Get the event type
event = notification.event
print(f"Webhook event received: {event}")
# Handle different event types
if event == "job.completed":
# notification.data will be DataNotificationPayloadJobCompleted at runtime
print(f"Job {notification.data.job.id} completed")
else:
print(f"Unexpected event type: {event}")
# Send success response
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"status": "OK"}).encode())
def log_message(self, format, *args): # noqa: N802
"""Suppress default logging."""
pass
if __name__ == "__main__":
server = HTTPServer(("localhost", PORT), WebhookHandler)
print(f"Test server listening on http://localhost:{PORT}")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server...")
server.shutdown()
```
</codeblock>
<codeblock lang="PHP">
```php PHP
use Chunkify\Client;
use Chunkify\Core\Exceptions\WebhookException;
$client = new Client(
webhookKey: 'your secret key',
);
$body = file_get_contents('php://input') ?: '';
$headers = getallheaders();
try {
// Unwrapping the webhook also verifies its signature
$notification = $client->webhooks->unwrap(
$body,
[
'webhook-signature' => $headers['webhook-signature'] ?? '',
'webhook-id' => $headers['webhook-id'] ?? '',
'webhook-timestamp' => $headers['webhook-timestamp'] ?? '',
],
);
switch ($notification->event) {
case 'job.completed':
$job = $notification->data->job;
$filesCount = count($notification->data->files);
printf("Job %s completed with %d files\n", $job->id, $filesCount);
break;
default:
error_log("Unexpected event type: {$notification->event}");
}
http_response_code(200);
echo 'OK';
} catch (WebhookException $error) {
error_log('Error unwrapping webhook: ' . $error->getMessage());
http_response_code(400);
echo 'Error unwrapping webhook';
}
```
</codeblock>
<codeblock lang="Go">
```go Go
import (
"fmt"
"io"
"net/http"
"github.com/chunkifydev/chunkify-go"
"github.com/chunkifydev/chunkify-go/option"
)
func handleWebhook(w http.ResponseWriter, r *http.Request) {
client := chunkify.NewClient(
option.WithWebhookKey("your secret key"),
)
// Read the request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusBadRequest)
return
}
// Unwrap the notification payload
notification, err := client.Webhooks.Unwrap(body, r.Header)
if err != nil {
http.Error(w, "Error unwrapping webhook", http.StatusBadRequest)
return
}
switch notification.Event {
case chunkify.UnwrapWebhookEventEventJobCompleted:
data := notification.Data.AsUnwrapWebhookEventDataNotificationPayloadJobCompleted()
fmt.Printf("Job %s completed with %d files\n", data.Job.ID, len(data.Files))
default:
fmt.Printf("unexpected event type: %s\n", notification.Event)
}
}
func main() {
// Register the webhook handler
http.HandleFunc("/webhook", handleWebhook)
// Start the server
port := ":8787"
fmt.Printf("Webhook server listening on http://localhost%s/webhook\n", port)
if err := http.ListenAndServe(port, nil); err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}
```
</codeblock>
</codegroup>
## Testing your webhook integration locally
To test your webhook integration safely the Chunkify CLI provides a way to proxy the notifications to your local server.
This way you can test your webhook behavior locally before deploying it to production.
You can see how to do that in the [CLI Guide](/docs/cli/integration#receiving-webhooks-notifications-locally).