Defining Publish Webhooks

Scrivito allows you to specify URLs you want to be requested each time a working copy of your CMS gets published. Such webhooks let you call web services in order to trigger actions related to the availability of new content such as prerendering your website, sending email notifications, posting messages to collaboration tools like Slack, etc.

You can provide up to ten URLs per CMS. Scrivito will contact each of them immediately after a publish occurred.

Configuring the publish webhooks

editing webhooks on the my.scrivito.com settings page

You can define your webhook URLs on the settings page of the corresponding website in your Dashboard.

The URLs need to start with https://.

For each URL a secret can be specified. If you do, the webhook call will include a JWT (JSON web token) – see Securing webhooks below.

my.scrivito.com settings page including publish webhook URLs

All webhook calls are logged. You can view the log directly on the settings page of your website.


Notification details

Every webhook is called with a POST request containing the following data:

Copy
{
  "event_type": "publish",
  "event_id": "01ab3h7429fc3ea7",
  "event_time": "2018-06-15T16:19:52+02:00",
  "tenant_id": "c945b6165725e8d1afe60f4ea62a0eae",
  "user_id": "editor@example.org",
  "workspace_id": "32382bb4a7099379",
  "workspace_title": "Awesome Feature X Documentation",
  "published_obj_ids": [
    "effe224bc4c9d397",
    "9024f773c11775e3",
    "1c7c7236426fdc28"
  ]
}
  • event_type – always publish.
  • event_id – a hexadecimal string identifying a particular publish.
  • event_time – the time stamp of the publish.
  • tenant_id – the ID of the CMS concerned.
  • user_id – the ID of the user who performed the publishing action (may be null).
  • workspace_id – the ID of the working copy that has been published.
  • workspace_title – the title of the working copy that has been published.
  • published_obj_ids – the list of the IDs of the CMS objects that have been modified in the working copy before publishing it.

Note that further properties may be added to the payload in the future, so your application should not reject requests because of excess properties in the payload.

If a webhook call fails, Scrivito retries the call up to nine times with an exponential backoff (1 minute, 2 minutes, 4 minutes, and so on).

Securing webhooks

For security reasons, you might want your web services to only accept requests originating from Scrivito. In order to achieve this, you can set up a secret token for each publish webhook in the Dashboard (see above).

If a secret token has been specified for a webhook, Scrivito sends a special HTTP header, Scrivito-Webhook-Signature, which contains a JWT allowing you to verify that the webhook request is authentic.

The JWT is signed using the secret token set in the Dashboard and applying the HS256 algorithm to it. To secure your web service, it should validate the JWT and make sure that the included SHA256 digest matches the request body.

The JWT includes the following fields:

  • exp – expiry time; used to prevent replay attacks.
  • sha256 – SHA256 hexdigest of the request body; use it to verify the authenticity of the request.

The JWT can be verified like shown the examples below.

Further fields may be added to the JWT in the future. Therefore, your code should not discard the JWT because of excess fields.

JavaScript (NodeJS) verification example

This implementation makes use of the jsonwebtoken package.

Copy
const JWT = require('jsonwebtoken');
const Crypto = require('crypto');

var secret = "your signature secret";

function isAuthenticRequest(headers, body) {
  let token = headers["Scrivito-Webhook-Signature"];
  let data;
  try {
    data = JWT.verify(token, secret, {algorithms: ['HS256']})
  } catch(e) {
    if (e instanceof JWT.JsonWebTokenError ||
        e instanceof JWT.TokenExpiredError) {
      return false;
    }
    throw e;
  }

  // data:
  // {
  //   sha256: '...',
  //   exp: ...
  // }

  let hash = Crypto.createHash('sha256');
  hash.update(body);
  return data.sha256 == hash.digest('hex');
}

Ruby verification example

This implementation uses the jwt gem.

Copy
require "digest"
require "jwt"

def authentic_request?(request, body)
  signature = request["Scrivito-Webhook-Signature"]
  return unless signature

  options = {algorithm: "HS256"}
  decoded = JWT.decode(signature, "your signature secret", true, options)

  ## decoded :
  ## [
  ##   { sha256: "...", exp: ... }, # this is the data in the token
  ##   { alg: "..." } # this is the header in the token
  ## ]

  decoded.first['sha256'] == Digest::SHA256.hexdigest(body)
rescue JWT::DecodeError
  false
end