Webhook integrations

Webhook allows you to publish projects with user-defined HTTP callbacks.

You can add an optional header that allows a webhook receiver to verify the content (secret needs to be set). Our webhook integration is very simple and waits for users' service to respond with 2xx; all other response codes are interpreted as an error. All requests have a 20-second timeout.

What you need

  • Publicly exposed HTTP endpoint

  • HTTP endpoint that can receive POST

  • Parse posted JSON-data sent from Vev (see example below)

When you receive a request from Vev, you can do whatever you want with the data sent.

Setup

Send only body When choosing β€œSend only body” we do not send required head tags and script tags; we only send the body content, and you have to decide yourself how you want to do with the sent body.

Secret

The secret is set by you and can be anything; it is used to verify that the content is sent from Vev. Read more in the signature section.

Endpoint security

Endpoint security is used to communicate with users' endpoint the correct way. We support non-secured endpoints, basic, and token-based. OpenID Connect (OIDC) is not implemented but might be in the future.

Signature

When publishing with webhook all requests from Vev is signed with a header X-Vev-Signature for clients to verify that Vev is the actual sender. In order to verify the signature from the request, you need the secret set by you in Vev and a crypto library to create HMAC digests.

X-Vev-Signature value consists of an algorithm to generate a hash and the signature itself, e.g 'sha512=super-long-hashed-value'. For now, we only support sha512 as an algorithm to create a signature. If you use the payload and secret with sha512, you get the same value as the signature.

Events

All requests are sent as type event, every event contains meta information for the event, and every event has a payload. We currently support two types of events: Ping and Publish. Ping is used for testing the webhook integration with your backend. Publish event contains all necessary HTML, CSS and JavaScript to display the project.

Event type:

interface Event {
id: string; // Unique id for event
hosting: string; // Hosting id
event: EventType; // Type of event - PING or PUBLISH
payload: any; // PublishPayload or PingPayload
}

Payload:

Ping payload:

interface PingPayload {
message: string;
html: string;
css: ExternalFile[];
js: ExternalFile[];
}

Publishing payload:

interface PublishPayload {
projectId: string;
version: number;
dir: string;
pages: Page[];
css: ExternalFile[];
js: ExternalFile[];
other: ExternalFile[];
}
interface Page {
html: string;
key: string;
title: string;
path: string;
cover?: string;
index: boolean;
settings?: any;
}

Common types:

interface ExternalFile {
url: string;
contentType?: string;
cacheControl?: string;
}

Example request from Vev

{
"id": "d34b0903-3344-4de8-bdac-998c8db064bc-0",
"hosting": "ZvSlvyLlf",
"event": "PING",
"payload": {
"css": [
{
"url": "https://cdn.vev.design/v/bcf21ff6/embed.css"
},
{
"url": "https://eit.vev.design/YIkBF6pT2/1fed44e6145826c5372035ea064ceda8.css"
},
{
"url": "https://eit.vev.design/YIkBF6pT2/YIkBF6pT2/63b7471e2e8de7083760d855ec200536.css"
}
],
"js": [
{
"url": "https://cdn.vev.design/v/bcf21ff6/vev.js"
},
{
"url": "https://eit.vev.design/YIkBF6pT2/87637b290c1566e267f0677ea1f99eec.js"
},
{
"url": "https://eit.vev.design/YIkBF6pT2/YIkBF6pT2/e957209b601c78c39045ffccf7b7b4b6.js"
}
],
"other": [],
"pages": [
{
"html": "<vevroot id=\"__vev\" embed data-reactroot=\"\"><div class=\"__p\" id=\"pabXn3ZFVTw\"><vev class=\"__section __s\" id=\"et5x4hNDEfg\"><div class=\"__wc\"><w class=\"section\" id=\"et5x4hNDEfgc\"><div class=\"children\"></div></w><div class=\"__c __sc\"><vev class=\"__w74c6cah\" id=\"eSaAhxKZIj\"><div class=\"__wc\"><a href=\"#eSaAhxKZIj\" class=\"__a external-link\" data-tween=\"false\"><w class=\"w74c6cah\" id=\"eSaAhxKZIjc\"><div class=\"fill\"></div></w></a></div></vev><vev class=\"__text\" id=\"ewylTEPqlT\"><div class=\"__wc\"><w class=\"text\" id=\"ewylTEPqlTc\"><div class=\"__stagger\"><h1><strong>Hello world!</strong></h1></div></w></div></vev><vev class=\"__rectangle __pre\" id=\"e9vyYbaZM5\"><div class=\"__wc\"><w class=\"rectangle\" id=\"e9vyYbaZM5c\"></w></div></vev></div></div></vev><vev class=\"__section __s\" id=\"e9bn4VoUtO\"><div class=\"__wc\"><w class=\"section\" id=\"e9bn4VoUtOc\"><div class=\"children\"></div></w><div class=\"__c __sc\"><vev class=\"__rectangle __pre\" id=\"eEz7pAreJD\"><div class=\"__wc\"><div class=\"__a w8rwjuzCtA\"><w class=\"rectangle\" id=\"eEz7pAreJDc\"></w></div></div></vev><vev class=\"__rectangle __pre\" id=\"eODDFoS_XZp\"><div class=\"__wc\"><div class=\"__a w8rwjuzCtA\"><w class=\"rectangle\" id=\"eODDFoS_XZpc\"></w></div></div></vev><vev class=\"__frame\" id=\"ekhnYO0yvv\"><div class=\"__wc\"><w class=\"frame\" id=\"ekhnYO0yvvc\"></w><div class=\"__c\"><vev class=\"__rectangle __pre\" id=\"eo3lNI20flT\"><div class=\"__wc\"><div class=\"__a w8rwjuzCtA\"><w class=\"rectangle\" id=\"eo3lNI20flTc\"></w></div></div></vev><vev class=\"__text\" id=\"eAdU-aQOwP\"><div class=\"__wc\"><div class=\"__a w8rwjuzCtA\"><w class=\"text\" id=\"eAdU-aQOwPc\"><div class=\"__stagger\"><p><strong>CLICK ME</strong></p></div></w></div></div></vev></div></div></vev><vev class=\"__rectangle __pre\" id=\"eTRLrwZSTo\"><div class=\"__wc\"><div class=\"__a w8rwjuzCtA\"><w class=\"rectangle\" id=\"eTRLrwZSToc\"></w></div></div></vev><vev class=\"__text\" id=\"e27SKQL9aB\"><div class=\"__wc\"><w class=\"text\" id=\"e27SKQL9aBc\"><div class=\"__stagger\"><h2>This is h2:</h2></div></w></div></vev><vev class=\"__text\" id=\"eE0MPCozhY\"><div class=\"__wc\"><w class=\"text\" id=\"eE0MPCozhYc\"><div class=\"__stagger\"><p><strong>HOVER ME</strong></p></div></w></div></vev></div></div></vev><vev class=\"__section __s\" id=\"eeVXexEccO\"><div class=\"__wc\"><w class=\"section\" id=\"eeVXexEccOc\"><div class=\"children\"></div></w><div class=\"__c __sc\"><vev class=\"__rectangle __pre\" id=\"eYUmz-SD2S\"><div class=\"__wc\"><w class=\"rectangle\" id=\"eYUmz-SD2Sc\"></w></div></vev><vev class=\"__shape\" id=\"eCGh7EkgmR\"><div class=\"__wc\"><a href=\"https://www.instagram.com/vev.design/?hl=nb\" class=\"__a external-link\" data-tween=\"false\"><w class=\"shape\" id=\"eCGh7EkgmRc\"><svg data-icon=\"true\" preserveAspectRatio=\"xMidYMid\" viewBox=\"0 0 448 512\"><path d=\"M448 80v352c0 26.5-21.5 48-48 48h-85.3V302.8h60.6l8.7-67.6h-69.3V192c0-19.6 5.4-32.9 33.5-32.9H384V98.7c-6.2-.8-27.4-2.7-52.2-2.7-51.6 0-87 31.5-87 89.4v49.9H184v67.6h60.9V480H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48z\"></path></svg></w></a></div></vev><vev class=\"__shape\" id=\"eqNRaJ3Vch\"><div class=\"__wc\"><a href=\"https://twitter.com/vevdesign\" class=\"__a external-link\" data-tween=\"false\"><w class=\"shape\" id=\"eqNRaJ3Vchc\"><svg data-icon=\"true\" preserveAspectRatio=\"xMidYMid\" viewBox=\"0 0 448 512\"><path d=\"M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-48.9 158.8c.2 2.8.2 5.7.2 8.5 0 86.7-66 186.6-186.6 186.6-37.2 0-71.7-10.8-100.7-29.4 5.3.6 10.4.8 15.8.8 30.7 0 58.9-10.4 81.4-28-28.8-.6-53-19.5-61.3-45.5 10.1 1.5 19.2 1.5 29.6-1.2-30-6.1-52.5-32.5-52.5-64.4v-.8c8.7 4.9 18.9 7.9 29.6 8.3a65.447 65.447 0 0 1-29.2-54.6c0-12.2 3.2-23.4 8.9-33.1 32.3 39.8 80.8 65.8 135.2 68.6-9.3-44.5 24-80.6 64-80.6 18.9 0 35.9 7.9 47.9 20.7 14.8-2.8 29-8.3 41.6-15.8-4.9 15.2-15.2 28-28.8 36.1 13.2-1.4 26-5.1 37.8-10.2-8.9 13.1-20.1 24.7-32.9 34z\"></path></svg></w></a></div></vev><vev class=\"__shape\" id=\"e_ttvBHGxX\"><div class=\"__wc\"><a href=\"https://www.vev.design/\" class=\"__a external-link\" data-tween=\"false\"><w class=\"shape\" id=\"e_ttvBHGxXc\"><svg data-icon=\"true\" preserveAspectRatio=\"xMidYMid\" viewBox=\"0 0 512 512\"><path d=\"M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM128 148c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40zm320 0c0 6.6-5.4 12-12 12H188c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h248c6.6 0 12 5.4 12 12v40z\"></path></svg></w></a></div></vev><vev class=\"__text\" id=\"eZo-3CYwEm\"><div class=\"__wc\"><w class=\"text\" id=\"eZo-3CYwEmc\"><div class=\"__stagger\"><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque a laoreet eros. Sed ut neque tincidunt quam varius vestibulum eget sit amet nibh. Nam mollis, dolor nec efficitur varius, ipsum est convallis turpis, eu dapibus mauris nisl non ligula. Ut erat tellus, ultricies vel dignissim pellentesque, mollis et sem. Etiam ac aliquet neque, commodo maximus ex. </p></div></w></div></vev></div></div></vev></div></vevroot>",
"index": true,
"key": "can-be-anything",
"path": "/",
"title": "Hello world"
}
]
}
}

Implementation example

NodeJS (server-side JavaScript)

crypto.js: (validation util)

const crypto = require('crypto');
​
function validateSignature(signature, algorithm, secret, payload) {
const hmac = crypto.createHmac(algorithm, secret);
const generatedSignature = hmac.update(JSON.stringify(payload)).digest('hex');
​
return signature === generatedSignature;
}
​
module.exports = {
validateSignature
};

index.js: (server code)

const express = require('express');
const fs = require('fs');
const mkdirp = require('mkdirp');
var bodyParser = require('body-parser');
const path = require('path');
​
const app = express();
const port = 8001;
​
app.use(bodyParser.json())
app.use(express.static('public'));
const validateSignature = require('./crypto').validateSignature;
app.use(express.json());
​
app.post('/webhook-receiver', (req, res) => {
const signature = req.get('X-Vev-Signature');
if (!signature) {
res.status('404').end();
return;
}
// verify signature
const s = signature.split('=');
const isValidSignature = validateSignature(
s[1],
s[0],
'<your-secret-here>',
req.body
);
​
if (isValidSignature === false) {
// do something here
return;
}
// parse payload
const payload = req.body.payload;
if (req.body.event === 'PUBLISH') {
payload.pages.forEach(page => {
const location = path.join('./public', page.index ? 'index' : page.path);
if (page.index) {
// is index.html page
fs.writeFileSync(location + '.html', page.html);
} else {
// create folder for location and create file inside folder.
mkdirp.sync(location);
fs.writeFileSync(location + '/index.html', page.html);
}
});
}
res.send({ message: 'I received your webhook!!' });
});
​
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Php

Validate function:

<?php
function validateSignature($payload){
$key = 'the shared secret key here';
$message = 'the message to hash here';
​
return hash_hmac('sha512', $message, $key) == $payload;
}
?>

Test webhook

Once webhook hosting is added, you can easily test your endpoint with Ping in the settings tab. Ping button is located next to the webhook log.​