Skip to content

Receiving webhooks

Webhooks are delivered using an HTTP Post request to a specified URL. The system will not follow any redirects. A delivery is considered successful if an HTTP status code in the 200-299 range is received. Any response body is not interpreted by the system except for debugging purposes. If a status code outside the 200-299 range is received, the delivery is considered a failure and will be retried. The same applies for connection issues like timeouts or unresolvable DNS names. The delivery will be retried up to an implementation defined maximum with exponential backoff. If the amount of retries is exhausted, the event will no longer be delivered to that webhook. After too many such failures, the webhook will be automatically deactivated.

Robustness

Webhook delivery is designed to be robust. The system will attempt to retry deliveries, if no success status was received. In rare circumstances a request might arrive in the target system and be processed, but the response fails to arrive, for example due to network failure. In such a case your system might receive the same event twice, because the IoT Cloud will attempt a re-delivery. Use the eventId property to detect if an event has already been processed by your system.

Debugging

You can see the most recent attempted deliveries to your webhooks in the admin interface.

A delivery attempt in the admin interface

Reactivating a webhook

If a webhook fails repeatedly, it will be automatically disabled. The user who created the Webhook will be notified via email. To reactivate the webhook, simply reactivate it via the Admin interface or the GraphQL API.

Security

When a webhook is received, you should ensure it is authentic using the included HMAC. This ensures that the message originates from the Cynox IoT Cloud, because it has been cryptographically signed using the secret key configured on the webhook. Additionally, it prevents replay attacks by including the timestamp of the request in the hash.

See the following example code for how to accomplish this.

Using Express

5 collapsed lines
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.use('/webhook', express.json({
verify(req, res, buf, encoding) {
const incomingHmac = req.headers['x-cynox-webhook-hmac'];
const incomingTsHeader = req.headers['x-cynox-webhook-timestamp'];
if (!incomingHmac || !incomingTsHeader) {
throw new Error('Missing headers');
}
const incomingTs = Date.parse(incomingTsHeader);
const myHmac = crypto.createHmac('sha256', 'your-secret-key')
.update(incomingTsHeader)
.update(buf)
.digest();
if (!crypto.timingSafeEqual(myHmac, Buffer.from(incomingHmac, 'hex'))) {
throw new Error('Invalid HMAC');
}
const delta = incomingTs - Date.now();
if (Math.abs(delta) > 5000) {
throw new Error('Outdated message');
}
}
}));
app.post('/webhook', (req, res) => {
console.log('Webhook received', req.body);
res.status(204).send();
});
2 collapsed lines
app.listen(3000);