Imagekit uses webhooks to notify your application when an event happens in your account. Webhooks are particularly useful for asynchronous events such as video encoding and extension processing during upload.
Steps to receive webhooks
Configure the webhook in your ImageKit dashboard.
Create a webhook endpoint as an HTTP endpoint (URL) on your server.
Handle requests from ImageKit by parsing each event object and returning 2xx response status codes.
How to configure a Webhook?
Go to developer options in ImageKit dashboard. Under Webhooks, you will see the list of webhook endpoints configured.
Click on the "Add new" button to create a new webhook endpoint.
Enter a valid HTTP(S) endpoint and click on "Create".
You should see the webhook endpoint in the list now.
To update an existing endpoint, click on ... and change the status.
Listen to Webhook
Use a tool like Ngrok to make your webhook endpoint publicly accessible for testing webhook implementation.
All webhook bodies are JSON encoded. The schema of the body may differ based on the event type, but the following fields are common.
Field
DataType
Description
type
string
Type of event.
id
string
Unique identifier of the event.
createdAt
string
Timestamp of the event in ISO8601 format.
data
JSON
Actual payload of event in JSON format.
Verify webhook signature
Webhook endpoints are publicly accessible, therefore it is essential to filter out malicious requests. We recommend using the webhook signature to verify the authenticity of the webhook request and payload.
To achieve this, Imagekit sends the signature in x-ik-signature request header in every webhook request. In your backend, you can generate the same signature using the webhook secret and compare it with the header value.
Keep webhook secret secure in your backend stored in an environment variable.
Verify signature with ImageKit SDK
You can use imagekit SDK to verify & parse webhook request payload.
constexpress=require('express');constImagekit=require('imagekit');// Webhook configsconstWEBHOOK_SECRET='whsec_...'; // Copy from Imagekit dashboardconstWEBHOOK_EXPIRY_DURATION=300*1000; // 300 seconds for exampleconstimagekit=newImagekit({ publicKey:'public_...', urlEndpoint:'https://ik.imagekit.io/imagekit_id', privateKey:'private_...',})constapp=express();app.post('/webhook',express.raw({ type:'application/json' }), (req, res) => {constsignature=req.headers["x-ik-signature"];constrequestBody=req.body;let webhookResult;try { webhookResult =imagekit.verifyWebhookEvent(requestBody, signature,WEBHOOK_SECRET); } catch (e) {// `verifyWebhookEvent` method will throw an error if signature is invalidconsole.log(e);res.status(400).send(`Webhook Error`); }const { timestamp,event } = webhookResult;// Check if webhook has expiredif (timestamp +WEBHOOK_EXPIRY_DURATION<Date.now()) {res.status(400).send(`Webhook Error`); }// Handle webhookswitch (event.type) {case'video.transformation.accepted': // It is triggered when a new video transformation request is accepted for processing. You can use this for debugging purposes.
break;case'video.transformation.ready': // It is triggered when a video encoding is finished, and the transformed resource is ready to be served. You should listen to this webhook and update any flag in your database or CMS against that particular asset so your application can start showing it to users.
break;case'video.transformation.error': // It is triggered if an error occurs during encoding. Listen to this webhook to log the reason. You should check your origin and URL-endpoint settings if the reason is related to download failure. If the reason seems like an error on the ImageKit side, then raise a support ticket at support@imagekit.io.
break;default:// ... handle other event typesconsole.log(`Unhandled event type ${event.type}`); }// Return a response to acknowledge receipt of the eventres.send();})app.listen(3000, () => {console.log(`Example app listening on port 3000`)})
Verify signature manually
Imagekit webhook request contains a x-ik-signature header like this:
The Timestamp of signature is a Unix timestamp in milliseconds, prefixed with t=.
The HMAC signature is prefixed with v1=.
Once you have retrieved the webhook signature from the request header & raw request body, you can verify the authenticity of the webhook request in the following steps:
Step 1: Extract each item from the x-ik-signature, by splitting on , separator.
Step 5: Compare the signature with the signature received in the x-ik-signature header. The webhook request is valid if they are the same.
Preventing replay attacks
When an attacker intercepts a webhook request, they can replay it multiple times with a valid signature.
To mitigate this, Imagekit webhook signature contains a timestamp. The Timestamp is generated before the webhook request is sent to your server.
The verifyWebhookEvent method in ImageKit SDK returns the Timestamp & parsed event object.
If the Timestamp is within the tolerance limit, the request can be considered valid, or you can reject it.
Optionally, a stronger approach is to use a nonce to prevent replay attacks. You can use Event ID as a nonce, it is guaranteed to be unique across all events. You can find the event ID in id field of the event object.
Sample Codes
constexpress=require('express');constImagekit=require('imagekit');// Webhook configsconstWEBHOOK_ENDPOINT='/webhook';constWEBHOOK_SECRET='whsec_...'; // Copy from Imagekit dashboardconstWEBHOOK_EXPIRY_DURATION=60*1000; // 60 secondsconstimagekit=newImagekit({ publicKey:'public_...', urlEndpoint:'https://ik.imagekit.io/imagekit_id', privateKey:'private_...',})constapp=express();app.post('/webhook',express.raw({ type:'application/json' }), (req, res) => {constsignature=req.headers['x-ik-signature'];constrawBody=req.body;// Verify webhook signaturelet webhookResult;try { webhookResult =imagekit.verifyWebhookEvent(rawBody, signature,WEBHOOK_SECRET); } catch (e) {// Failed to verify webhookreturnres.status(401).send(`Webhook error: ${e.message}`); }const { timestamp,event } = webhookResult;// Check if webhook has expiredif (timestamp +WEBHOOK_EXPIRY_DURATION<Date.now()) {// Stall webhookreturnres.status(401).send('Webhook signature expired'); }// Handle webhookswitch (event.type) {case'video.transformation.accepted': // It is triggered when a new video transformation request is accepted for processing. You can use this for debugging purposes.
break;case'video.transformation.ready': // It is triggered when a video encoding is finished, and the transformed resource is ready to be served. You should listen to this webhook and update any flag in your database or CMS against that particular asset so your application can start showing it to users.
break;case'video.transformation.error': // It is triggered if an error occurs during encoding. Listen to this webhook to log the reason. You should check your origin and URL-endpoint settings if the reason is related to download failure. If the reason seems like an error on the ImageKit side, then raise a support ticket at support@imagekit.io.
break;// ... handle other event typesdefault:console.log(`Unhandled event type ${event.type}`); }// Acknowledge webhook is received and processed successfullyres.status(200).end();});app.listen(3000, () => {console.log("Server listening on port 3000");console.log(`Webhook endpoint: 'http://localhost:${PORT}${WEBHOOK_ENDPOINT}'`,'Do replace 'localhost' with public endpoint' );});
constfastify=require('fastify');constfastifyRawBody=require('fastify-raw-body');constImagekit=require('imagekit');// Webhook configsconstWEBHOOK_ENDPOINT='/webhook';constWEBHOOK_SECRET='whsec_...'; // Copy from Imagekit dashboardconstWEBHOOK_EXPIRY_DURATION=60*1000; // 60 secondsconstimagekit=newImagekit({ publicKey:'public_...', urlEndpoint:'https://ik.imagekit.io/imagekit_id', privateKey:'private_...',})constserver=fastify();conststartServer=async (port) => {// Use 'fastify-raw-body' package to retrive raw request body as utf8 stringawaitserver.register(fastifyRawBody, { routes: [WEBHOOK_ENDPOINT] });// Add webhook endpoint routeserver.route({ method:'POST', url:WEBHOOK_ENDPOINT,handler: (req, res) => {// Handle webhookconstsignature=req.raw.headers['x-ik-signature'];constrawBody=req.body;// Verify webhook signaturelet webhookResult;try { webhookResult =imagekit.verifyWebhookEvent(rawBody, signature,WEBHOOK_SECRET); } catch (e) {// Webhook verification failedreturnres.status(401).send(`Webhook error: ${e.message}`); }const { timestamp,event } = webhookResult;// Check if webhook has expiredif (timestamp +WEBHOOK_EXPIRY_DURATION<Date.now()) {// Stall webhookreturnres.status(401).send('Webhook signature expired'); }// Handle webhookswitch (event.type) {case'video.transformation.accepted':console.log('Video transformation request accepted');break;case'video.transformation.ready':console.log('Video transformation ready');break;case'video.transformation.error':console.log('Video transformation error');break; }// Acknowledge webhook is received and processed successfullyres.status(200).send(); }, });awaitserver.listen({ port });};startServer(3000).then(() => {console.log("Server listening on port 3000");console.log(`Webhook endpoint: 'http://localhost:${PORT}${WEBHOOK_ENDPOINT}'`,'Do replace 'localhost' with public endpoint' );});