Webhooks

Usage

You can configure webhooks in your Developer Portal.

Retry logic

For every failed web hook delivery (response code different from 200, 201, 204) Filestack will retry webhook delivery three times:

  • in 5 minutes
  • in 30 minutes
  • in 12 hours

After that webhook is marked as not delivered.

Events and Messages

The following are examples of all event payloads that your application can receive.

Upload

Event triggers anytime a file is uploaded to your application.

{
    "id": 30813791,
    "action": "fp.upload",
    "timestamp": 1521733983,
    "text": {
        "container": "filestack-uploads-persist-production",
        "url": "https://cdn.filestackcontent.com/HANDLE",
        "filename": "FILENAME",
        "client": "Computer",
        "key": "S3_KEY",
        "type": "MIMETYPE",
        "status": "Stored",
        "size": 158584
    }
}

Convert

Event is triggered upon any image or document conversion.

{
    "id": 30814778,
    "action": "fp.converse",
    "timestamp": 1521734642,
    "text": {
        "url": "/CONVERSION_TASKS/HANDLE",
        "link": {
            "url": "https://www.filestackapi.com/api/file/HANDLE",
            "handle": "HANDLE",
            "filename": "FILENAME",
            "mimetype": "MIMETYPE",
            "path": "FILESTACK_S3_PATH",
            "provider": "internal_v2",
            "size": 158584
        }
    }
}

Overwrite

Event is triggered upon any file overwrite.

{
    "id": 30815340,
    "action": "fp.overwrite",
    "timestamp": 1521735062,
    "text": {
        "mimetype": "MIMETYPE",
        "url": "https://cdn.filestackcontent.com/HANDLE",
        "isWriteable": true,
        "filename": "FILENAME",
        "client": "Computer",
        "size": 436688
    }
}

Delete

Event is triggered upon any file deletion

{
    "id": 30814508,
    "action": "fp.delete",
    "timestamp": 1521734463,
    "text": {
        "url": "https://cdn.filestackcontent.com/HANDLE"
    }
}

Workflow

Event is triggered for every workflow job execution. This example includes results of the virus detection task.

{
    "id": 61380634,
    "action": "fs.workflow",
    "timestamp": 1548214263,
    "text": {
        "workflow": "687r07d2-5f84-44a0-b20b-1c29a1deb2ab",
        "createdAt": "2019-01-23T03:30:55.400729934Z",
        "updatedAt": "2019-01-23T03:30:57.437268735Z",
        "sources": [
            "VJQTvCboRVivWJ2BpKgd"
        ],
        "results": {
            "virus_detection_1548213592150": {
                "data": {
                    "infected": false,
                    "infections_list": []
                }
            }
        },
        "status": "Finished"
    }
}

Video Conversion

Event is triggered when an audio or video conversion is finished.

{
   "status":"completed",
   "message":"Done",
   "data":{
      "thumb":"https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
      "thumb100x100":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:100,h:100,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
      "thumb200x200":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:200,h:200,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
      "thumb300x300":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:300,h:300,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
      "url":"https://cdn.filestackcontent.com/VgvFVdvvTkml0WXPIoGn"
   },
   "metadata":{
      "result":{
         "audio_channels":2,
         "audio_codec":"vorbis",
         "audio_sample_rate":44100,
         "created_at":"2015/12/21 20:45:19 +0000",
         "duration":10587,
         "encoding_progress":100,
         "encoding_time":8,
         "extname":".webm",
         "file_size":293459,
         "fps":24,
         "height":260,
         "mime_type":"video/webm",
         "started_encoding_at":"2015/12/21 20:45:22 +0000",
         "updated_at":"2015/12/21 20:45:32 +0000",
         "video_bitrate":221,
         "video_codec":"vp8",
         "width":300
      },
      "source":{
         "audio_bitrate":125,
         "audio_channels":2,
         "audio_codec":"aac",
         "audio_sample_rate":44100,
         "created_at":"2015/12/21 20:45:19 +0000",
         "duration":10564,
         "extname":".mp4",
         "file_size":875797,
         "fps":24,
         "height":360,
         "mime_type":"video/mp4",
         "updated_at":"2015/12/21 20:45:32 +0000",
         "video_bitrate":196,
         "video_codec":"h264",
         "width":480
      }
   },
   "timestamp":"1453850583",
   "uuid":"638311d89d2bc849563a674a45809b7c"
}

Security

Adding security to webhooks can give you the ability to verify that Filestack was source of the webhook.

In order to validate your webhooks you should follow this procedure:

  • Create a secret key for your webhook in your Developer Portal like the following screenshot.

  • Create a string with the template “[FS-Timestamp].[Content]” where [FS-Timestamp] is the timestamp value in the request header from Filestack, and [Content] is the raw content of the request.

  • Generate the digital signature of the above-generated string using HMAC-SHA256 algorithm with your webhook secret key from developer portal. You can use any application to sign your string with its secret key using HMAC-SHA256 algorithm. The image below shows an example of generated digital signature.

  • Check the generated digital signature with the value of FS-Signature in the header of received webhook. If it matches with FS-Signature that means your received webhook is from Filestack. For more information please check our code snippets.
Hint: FS-Timestamp may not be the same with the timestamp in the body of webhook. FS-Timestamp and FS-Signature are provided in the header of the webhook.

Webhook Receiver

In order to receive webhooks from Filestack and validate them, use the following Python example using Filestack Python SDK and Flask library.

  • Requirements:

    • filestack-python>=2.7.0,<3.0.0
    • flask>=1.0.3

      from flask import Flask, request, abort
      from filestack import Client
      
      app = Flask(__name__)
      
      
      @app.route('/webhook', methods=['POST'])
      def webhook():
      if request.method == 'POST':
      resp = Client.verify_webhook_signature('<SECRET>', request.data, dict(request.headers))
      if not resp['valid'] and resp['error'] is None:
          print('Webhook signature is invalid')
      elif resp['error']:
          print('Please check input params: {}'.format(resp['error']))
      else:
          print('Webhook is valid and was generated by Filestack')
      return '', 200
      else:
      abort(400)
      
      
      if __name__ == '__main__':
      app.run()

Using the provided example in Python you can validate webhooks by using the following cURL command example:

curl -X POST 127.0.0.1:5000/webhook \
     -d '{"id": 77099474, "action": "fp.upload", "timestamp": 1559283242, "text": {"container": "container", "url": "https://cdn.filestackcontent.com/handle", "filename": "cloud.png", "client": "Computer", "key": "cloud.png", "type": "image/png", "size": 17548}}' \
     -H 'Content-Type: application/json' \
     -H 'FS-Timestamp: 1559283242' \
     -H 'FS-Signature: 192ff14ef4e56fffe2cead7d0b306fbcb3a227da419f765e20fad10540080753'

In this example, the signature is generated for secret: secret.

Raw Webhook Check

Node.js

  • package.json

    {
    "name": "whdecoder",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "author": "",
    "license": "ISC",
    "dependencies": {
        "body-parser": "^1.19.0",
        "express": "^4.17.1"
    }
    }
  • index.js

    const express = require('express');
    const bodyParser = require('body-parser');
    const crypto = require('crypto');
    
    const app = express();
    app.use(bodyParser.raw({type: '*/*'}));
    
    const port = 5000;
    
    const secret = '<SECRET>';
    
    app.post('/webhook', (req, res) => {
    
    console.log(`Request Signature: ${req.header('FS-Signature')}`);
    console.log(`Request Timestamp: ${req.header('FS-Timestamp')}`);
    console.log(`RawBody ${req.body}`)
    const hash = crypto
        .createHmac('sha256', secret)
        .update(`${req.header('FS-Timestamp')}.${req.body}`)
        .digest('hex');
    
    console.log(`SIGNATURE IS ${hash === req.header('FS-Signature') ? 'OK' : 'NOT OK!'}`, hash, req.header('FS-Signature'));
    
    res.json(req.body)
    })
    
    app.listen(port, () => console.log(`Example app listening on port ${port}!`))
    

PHP

<?php

$postdata = file_get_contents("php://input");
$signature = $_SERVER['HTTP_FS_SIGNATURE'];
$timestamp = $_SERVER['HTTP_FS_TIMESTAMP'];
$secret = '<SECRET>';

$res = 'Webhook signature: '.((hash_hmac('sha256', $timestamp . '.' . $postdata, $secret) == $signature) ? "CORRECT" : "INCORRECT");
echo $res;
error_log($res);

For more information please contact Filestack Support

Webhook Examples

You can see some exmaples of generating the string for digital signature in different webhooks.

Workflows webhook

  • Secret: “SecretSecretSecretAA”

  • Webhook request content:

    {
    "id": 1000,
    "timestamp": 1558123673,
    "text": {
        "jobid": "jobid-jobid-jobid-jobid-jobid",
        "createdAt": "2020-04-26T10:53:02.936164785Z",
        "workflow": "workflowid-workflowid-workflowid-workflowid",
        "results": {
            "border": {
                "url": "https://cdn.filestackcontent.com/Aaaaaaaaaaaaaaaaaaaaaaz/wf://workflowid-workflowid-workflowid-workflowid/jobid-jobid-jobid-jobid-jobid/aaaaaaaaaaaaaaaaaaaa",
                "mimetype": "image/png"
            },
            "metadata": {
                "data": {
                    "size": 64016,
                    "filename": "filename.jpg",
                    "uploaded": 1501181734097.6802,
                    "mimetype": "image/jpeg"
                }
            },
            "circle": {
                "url": "https://cdn.filestackcontent.com/Aaaaaaaaaaaaaaaaaaaaaaz/wf://workflowid-workflowid-workflowid-workflowid/jobid-jobid-jobid-jobid-jobid/bbbbbbbbbbbbbbbbbbbb",
                "mimetype": "image/png"
            }
        },
        "sources": [
            "Handle1",
            "Handle2"
        ],
        "updatedAt": "2020-04-26T10:53:06.524680403Z",
        "status": "Finished"
    },
    "action": "fs.workflow"
    }
  • Signing string:

    1559204277.{"id": 1000, "timestamp": 1558123673, "text": {"jobid": "jobid-jobid-jobid-jobid-jobid", "createdAt": "2020-04-26T10:53:02.936164785Z", "workflow": "workflowid-workflowid-workflowid-workflowid", "results": {"border": {"url": "https://cdn.filestackcontent.com/Aaaaaaaaaaaaaaaaaaaaaaz/wf://workflowid-workflowid-workflowid-workflowid/jobid-jobid-jobid-jobid-jobid/aaaaaaaaaaaaaaaaaaaa", "mimetype": "image/png"}, "metadata": {"data": {"size": 64016, "filename": "filename.jpg", "uploaded": 1501181734097.6802, "mimetype": "image/jpeg"}}, "circle": {"url": "https://cdn.filestackcontent.com/Aaaaaaaaaaaaaaaaaaaaaaz/wf://workflowid-workflowid-workflowid-workflowid/jobid-jobid-jobid-jobid-jobid/bbbbbbbbbbbbbbbbbbbb", "mimetype": "image/png"}}, "sources": ["Handle1", "Handle2"], "updatedAt": "2020-04-26T10:53:06.524680403Z", "status": "Finished"}, "action": "fs.workflow"}
    
  • Request header:

    FS-Signature: a841816d3ad7782ccc07434681d1f19649dc8a3d60cc32b33f6ee0bcc8052272
    FS-Timestamp: 1559204277
    
    

Upload webhook

  • Secret: “SecretSecretSecretAA”

  • Webhook request content:

    {
    "text": {
        "url": "https://cdn.filestackcontent.com/Handle",
        "type": "image/jpeg",
        "size": 100000,
        "container": "your-bucket",
        "key": "kGaeljnga9wkysK6Z_filename.jpg",
        "filename": "filename.jpg",
        "status": "Stored",
        "client": "Computer"
    },
    "timestamp": 1558123673,
    "id": 1000,
    "action": "fp.upload"
    }
  • Signing string:

    1559204382.{"text": {"url": "https://cdn.filestackcontent.com/Handle", "type": "image/jpeg", "size": 100000, "container": "your-bucket", "key": "kGaeljnga9wkysK6Z_filename.jpg", "filename": "filename.jpg", "status": "Stored", "client": "Computer"}, "timestamp": 1558123673, "id": 1000, "action": "fp.upload"}
    
  • Request header:

    FS-Signature: 4e0cb808e0e4f1ab6cbcf9b38841c7aca09b2b938b40ac719a8fc3ce7c644923
    FS-Timestamp: 1559204382
    

Video Convert webhook

  • Secret: “SecretSecretSecretAA”

  • Webhook request content:

    {
    "uuid": "15c58ienbuenvjd228ba81c11fd1a156",
    "id": 1000,
    "metadata": {
        "result": {
            "updated_at": "2019/05/17 07:03:23 +0000",
            "extname": ".mp4",
            "started_encoding_at": "2019/05/17 07:03:16 +0000",
            "mime_type": "video/mp4",
            "width": 270,
            "audio_sample_rate": 44100,
            "encoding_time": 6,
            "file_size": 91697,
            "audio_channels": 1,
            "encoding_progress": 100,
            "created_at": "2019/05/17 07:03:13 +0000",
            "audio_codec": "aac",
            "height": 480,
            "video_bitrate": 225,
            "audio_bitrate": 129,
            "duration": 2047,
            "fps": 24,
            "video_codec": "h264"
        },
        "source": {
            "updated_at": "2019/05/17 07:03:23 +0000",
            "extname": ".mp4",
            "mime_type": "video/mp4",
            "width": 270,
            "audio_sample_rate": 44100,
            "file_size": 330154,
            "audio_channels": 1,
            "created_at": "2019/05/17 07:03:13 +0000",
            "audio_codec": "aac",
            "height": 480,
            "video_bitrate": 311,
            "audio_bitrate": 128,
            "duration": 5968,
            "fps": 24,
            "video_codec": "h264"
        }
    },
    "status": "completed",
    "data": {
        "thumb": "https://cdn.filestackcontent.com/Handle1",
        "thumb200x200": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:200,h:200,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1",
        "thumb300x300": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:300,h:300,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1",
        "thumb100x100": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:100,h:100,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1",
        "url": "https://cdn.filestackcontent.com/Handle2"
    },
    "timestamp": "1558123673"
    }
  • Signing string:

    1559204470.{"uuid": "15c58ienbuenvjd228ba81c11fd1a156", "id": 1000, "metadata": {"result": {"updated_at": "2019/05/17 07:03:23 +0000", "extname": ".mp4", "started_encoding_at": "2019/05/17 07:03:16 +0000", "mime_type": "video/mp4", "width": 270, "audio_sample_rate": 44100, "encoding_time": 6, "file_size": 91697, "audio_channels": 1, "encoding_progress": 100, "created_at": "2019/05/17 07:03:13 +0000", "audio_codec": "aac", "height": 480, "video_bitrate": 225, "audio_bitrate":129, "duration": 2047, "fps": 24, "video_codec": "h264"}, "source": {"updated_at": "2019/05/17 07:03:23 +0000", "extname": ".mp4", "mime_type": "video/mp4", "width": 270, "audio_sample_rate": 44100, "file_size": 330154, "audio_channels": 1, "created_at": "2019/05/17 07:03:13 +0000", "audio_codec": "aac", "height": 480, "video_bitrate": 311, "audio_bitrate": 128, "duration": 5968, "fps": 24, "video_codec": "h264"}}, "status": "completed", "data": {"thumb": "https://cdn.filestackcontent.com/Handle1", "thumb200x200": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:200,h:200,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1", "thumb300x300": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:300,h:300,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1", "thumb100x100": "https://process.filestackapi.com/Aaaaaaaaaaaaaaaaaaaaaaz/resize=w:100,h:100,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/Handle1", "url": "https://cdn.filestackcontent.com/Handle2"}, "timestamp": "1558123673"}
    
  • Request header:

    FS-Signature: d02afaaaab60786abc68929d812e9985a5d57161dbf10fe2842656f4e90e6c76
    FS-Timestamp: 1559204470