Skip to main content

Webhooks

Warn:

If you are using Legacy Webhook we recommend you update it to the latest to take advantage of all the latest event triggers.

What are Webhooks?

A webhook is a way for one application to send other applications immediate information. If you would like to react to events within Storyblok in another service, you need a way to inform these services. Storyblok offers webhooks to call to the outside world. This mechanism is very useful when you want to clear a cache or start a build process, for example whenever new content is published or updated.

app.storyblok.com
Screenshot of Storyblok Webhooks tab
1
2
3

Storyblok Webhooks tab

In Storyblok creating Webhook is pretty straightforward. You can follow the steps in the above image to create your first Webook.

app.storyblok.com
Storyblok Create new Webhook
1
2
3

Storyblok Create new Webhook

In order to create a Webhook you have to fill 3 required fields.

app.storyblok.com
Webhook with all the triggers enabled

Webhook with all the triggers enabled

Once added, a POST request will be sent with a JSON payload to the specified URL. It expects to receive a 2xx status code and Content-Type: application/json as a response.

Webhooks will not be retried if the service fails to process the event. Publish and/or save are single events and retries could have unwanted side effects. If the endpoint doesn’t return a status 2xx, an email with a message that the webhook failed will be sent to the space owner.

If you plan to execute a long-running task, it is recommended that a response is sent immediately (e.g., 202 - Accepted ) instead of waiting until the task is finished. The webhook will time out and trigger an error message after 120 seconds.

Available Triggers

Here is a list of all the available triggers with their respective JSON payload.

Story

  1. Story published

  2. Story unpublished

  3. Story deleted

  4. Story saved

  5. Story moved

  • published: triggered when a story is published

JSON payload
{
  "text": "The user username@domain.com published the Story Test (test)\nhttps://app.storyblok.com/#/me/spaces/123/stories/0/0/1234",
  "action": "published",
  "space_id": 123,
  "story_id": 1234,
  "full_slug": "test"
}
  • unpublished: triggered, when a story is unpublished

JSON payload
{
  "text": "The user username@domain.com unpublished the Story Test (test)\nhttps://app.storyblok.com/#/me/spaces/123/stories/0/0/1234",
  "action": "unpublished",
  "space_id": 123,
  "story_id": 1234,
  "full_slug": "test"
}
  • deleted: triggered, when a story is deleted (dropdown in the content overview)

JSON payload
{
  "text": "The user username@domain.com deleted the Story Test (test)\nhttps://app.storyblok.com/#/me/spaces/123/stories/0/0/1234",
  "action": "deleted",
  "space_id": 123,
  "story_id": 1234
}
  • saved: triggered when a story is created or an existing story is saved

JSON payload

{
  "text": "The user username@domain.com saved the Story Test (test)\nhttps://app.storyblok.com/#/me/spaces/123/stories/0/0/1234",
  "action": "saved",
  "space_id": 123,
  "story_id": 1234
}
  • moved: triggered when a story is moved from a folder or to a folder

JSON payload
{
  "text": "The user username@domain.com moved the Story Test (test)\nhttps://app.storyblok.com/#/me/spaces/123/stories/0/0/1234",
  "action": "moved",
  "space_id": 123,
  "story_id": 1234
}

Datasource

  1. Entry saved

  • entries_updated: triggered when a new datasource entry is saved or added.

JSON payload
{
  "text": "The datasource entry red has been saved.",
  "action": "entries_updated",
  "space_id": 123,
  "datasource_slug": "background-colors"
}
  • datasource_entry_saved: triggered, when a data source entry is saved or added (legacy webhook). 

JSON payload
{
  "text": "The datasource entry red has been saved.",
  "action": "datasource_entry_saved",
  "space_id": 123,
  "datasource_slug": "background-colors"
}

Asset

  1. Asset created

  2. Asset replaced

  3. Asset deleted

  4. Asset restored

  • created: triggered when an asset is uploaded

JSON payload
{
    "text": "The user username@email.com created the Asset acm_uploadzzzzz.png\nhttps: //a.storyblok.com/f/132139/1440x840/23e366ce67/acm_uploadzzzzz.png",
    "action": "created",
    "asset_id": 10288746,
    "space_id": 123
}
  • replaced: triggered when an asset is replaced

JSON payload
{
    "text": "The user username@email.com replaced the Asset acm_uploadzzzzz.png\nhttps: //a.storyblok.com/f/132139/1440x840/23e366ce67/acm_uploadzzzzz.png",
    "action": "replaced",
    "asset_id": 10288746,
    "space_id": 123
}
  • deleted: triggered when an asset is deleted

JSON payload
{
    "text": "The user username@email.com deleted the Asset acm_uploadzzzzz.png\nhttps: //a.storyblok.com/f/132139/1440x840/23e366ce67/acm_uploadzzzzz.png",
    "action": "deleted",
    "asset_id": 10288746,
    "space_id": 123
}
  • restored: triggered when an asset is restored

JSON payload
{
    "text": "The user username@email.com restored the Asset acm_uploadzzzzz.png\nhttps: //a.storyblok.com/f/132139/1440x840/23e366ce67/acm_uploadzzzzz.png",
    "action": "restored",
    "asset_id": 10288746,
    "space_id": 123
}

User management

  1. User added

  2. User removed

  3. User’s roles updated

  • added: triggered when a new user is added to the space

JSON payload
{
    "text": "The user newuser@gmail.com was added by username@email.com",
    "action": "added",
    "user_id": 127771,
    "space_id": 123
}

  • removed: triggered when a user is removed from the space

JSON payload
{
    "text": "The user newuser@gmail.com was removed by username@email.com",
    "action": "removed",
    "user_id": 127771,
    "space_id": 123
}

  • roles_updated: triggered when a user role is updated

JSON payload
{
    "text": "The user newuser@gmail.com was roles_updated by username@email.com",
    "action": "roles_updated",
    "user_id": 127771,
    "space_id": 123
}

Workflow

  1. Workflow changed

  • stage.changed: triggered when the workflow stage of a story changes.

JSON payload
{
  "text": "The workflow stage of story Test has been changed to Reviewing.",
  "action": "stage.changed",
  "space_id": 123,
  "story_id": 1234,
  "workflow_name": "Default",
  "workflow_stage_name": "Reviewing"
}

  • workflow_stage_changed: triggered, when the workflow stage of a story changes (legacy webhook)

JSON payload
{
  "text": "The workflow stage of story Test has been changed to Reviewing.",
  "action": "workflow_stage_changed",
  "space_id": 123,
  "story_id": 1234,
  "workflow_name": "Default",
  "workflow_stage_name": "Reviewing"
}

Pipeline

Important:

To use Pipeline webhooks the Pipelines App needs to be installed first.

  1. Pipeline deployed

  • deployed: triggered, when a pipeline stage is deployed

JSON Payload
{
  "text": "The branch Staging has been deployed.",
  "action": "deployed",
  "space_id": 123,
  "branch_id": 9218
}
  • branch_deployed: triggered, when a pipeline stage is deployed(legacy webhook)

JSON Payload
{
    "text": "The branch Staging has been deployed.",
    "action": "branch_deployed",
    "space_id": 123,
    "branch_id": 123
}

Releases

Important:

To use Release webhooks, the Releases App needs to be installed first.

  1. Release merged

  • merged: triggered when a release is merged into the currently released content

JSON payload
{
  "text": "The release Test has been merged.",
  "action": "merged",
  "space_id": 123,
  "release_id": 109114
}
  • release_merged: triggered, when a release is merged into the currently released content(legacy webhook)

JSON payload
{
    "text": "The release Test has been merged.",
    "action": "release_merged",
    "space_id": 123,
    "release_id": 123
}

Tasks

Important:

To use Task webhooks, the Tasks App needs to be installed.

The Task App is used to create automation tasks which then execute a webhook. Tasks are triggered from the user interface by clicking the “Execute” button. To configure a webhook using the Task-Manager App, you need to create a new task {1}

app.storyblok.com
Task-Manager App
1

Task-Manager App

app.storyblok.com
Create new Task in Task-Manager App
1
2
3

Create new Task in Task-Manager App

  • task_execution: trigger a request from Storyblok

JSON Payload
{
    "task": {
        "id": 123,
        "name": "Trigger Webhook"
    },
    "text": "The user test@domain.com executed the task Trigger Webhook",
    "action": "task_execution",
    "space_id": 123,
    "dialog_values": null
}
  • task_execution with user dialog: the payload contains additional data in dialog_values. When setting up a task, it is possible to add a dialog. The provided user input will be sent to the endpoint as dialog_values.

JSON payload
{
    "task": {
        "id": 123,
        "name": "Trigger Webhook"
    },
    "text": "The user test@domain.com executed the task Trigger Webhook",
    "action": "task_execution",
    "space_id": 123,
    "dialog_values": {
        "name": "Test",
        "environment": "dev"
    }
}

Securing a Webhook

Note:

In order to set the webhook secret you require an Entry plan or above.

To protect your services, it is always a good idea to make sure that only requests from expected sources are processed. To protect your webhook endpoints, it is necessary to verify if the request is really coming from Storyblok. It is possible to verify the sender of the webhook by verifying the signature that is sent along with the payload and generated with a shared secret key (webhook secret).

app.storyblok.com
Define Webhook secret
1

Define Webhook secret

app.storyblok.com
Define Webhook secret legacy
1

Define Webhook secret legacy

We recommend using a random string with at least 20 characters (e.g. output of this command openssl rand -hex 20). Once the secret is inserted, press the Save button or hit enter to permanently store the data.

Storyblok will add a signature to every webhook request once the webhook secret {1} is configured. The signature is sent in the webhook-signature header of the request. If the webhook secret is not set, the header remains empty.

In your webhook endpoint, you can now check if the webhook-signature was generated with the webhook secret. To use this example, you need to define the environment variable STORYBLOK_WEBHOOK_SECRET with the value used as webhook secret.

Verfify webhook signature - JS
import { createHmac } from 'node:crypto'
// signature from header 'webhook-signature'
const signature = '64c6153b5bff67......7d01f244bb7ad253e563'
// body of webhook request
const body = {
    "text": "The user ... published the Story ...",
    "action": "published",
    "space_id": 123,
    "story_id": 123
};
 
try {
  verifySignature(body, signature);
} catch(err) {
  console.log('Failure: ' + err.message);
}
 
function verifySignature(body, signature) {
  // webhookSecret configured in Storyblok settings
  const webhookSecret = process.env.STORYBLOK_WEBHOOK_SECRET; // 🙈 secrets shouldn't be in your code
  let bodyHmac = createHmac('sha1', webhookSecret)
    .update(JSON.stringify(body))
    .digest('hex');
 
  if (bodyHmac !== signature) {
    throw new Error('Signature mismatch!');
  }
}
Verify webhook signature - Ruby
class WebhookController < ApplicationController
  def create
    verify_signature(request.raw_post)
    # do stuff when verified
    render json: {success: true}
  end
 
  def verify_signature(payload_body)
    signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), ENV[‘STORYBLOK_WEBHOOK_SECRET'], payload_body)
    raise StandardError, "Signature check failed!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_WEBHOOK_SIGNATURE'])
  end
end

Processing Webhooks

There are multiple ways to use the Storyblok webhooks. For instance, they can be sent to specific services to trigger build scripts (e.g. Netlify, Vercel), they can send a notification (e.g. Slack, Discord), or you can even use your own handler to process a webhook.

Depending on the tasks you would like to execute, there are different options for handling an incoming webhook. In this case, it heavily depends on what you would like to trigger with the webhook. There are a couple of ways to execute your logic:

  • By hosting a full-blown server. This depends heavily on what technology you are using.

  • By using serverless functions. Since webhooks logic usually triggers a concise program, it's a perfect case for serverless functions. Some of the players are AWS Lambda, Google Cloud, Azure Functions, Webtask.io, Vercel, and Netlify Functions.

  • By using low- or no-code environments. Low- and no-code environments already provide predefined building blocks that can be used to create your own flows. Example providers are pipedream, n8n, IFTTT, and Zapier.

learn:

Webhooks can, for example, be used to trigger build processes on Netlify or Vercel. Start by reading about how Netlify - Build Hooks or Vercel - Deploy Hooks work.

Distribute Events

Sometimes it is not enough to trigger only one endpoint. To send a single event to multiple environments, a service that distributes the event is required. An example of how such a service could look can be found in this GitHub project: https://github.com/DominikAngerer/webhook-router.

Debugging Webhooks

One thing that makes development much easier, is a sensible way of finding out what is happening when something doesn’t work as expected. To allow you to get this information quickly, we can offer two strategies to debug webhooks:

Webhook logs

app.storyblok.com
Webhook logs
1

Webhook logs

app.storyblok.com
Webhook logs legacy
1

Webhook logs legacy

Logs show you which requests have been sent with which payload.

app.storyblok.com
Webhook logs list

Webhook logs list

To see the JSON payload you can click on a log item.

Debug Endpoint

Sometimes it is unclear if the correct payload arrives at the endpoint or if the event is triggered as expected. In this case, you can use webhook.site or RequestBin to test the request information you get from webhooks. The tutorial “How to use RequestBin.com to work with Webhooks” covers this topic.