Unreal Cloud Docs

Webhooks

Receive signed build events on your own server, bot or automation tool.

A webhook sends a signed JSON request to a URL you choose whenever a build event happens. Use it to drive your own bots, pipelines, or tools like Zapier, Make and n8n.

Webhooks share their events, testing and delivery log with Slack and Discord — see Notifications.

Add a webhook

In the project's Settings tab, under Notifications, click Add webhook and enter your endpoint's URL. It must use HTTPS and point to a public address.

Unreal Cloud then shows the webhook's signing secret, starting with whsec_. You can reveal or copy it again later from the webhook's card.

The request

Each event is a POST with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
User-AgentUnrealCloud-Webhooks/1.0
webhook-idA unique ID for this delivery. Use it to ignore duplicates.
webhook-timestampWhen it was sent, in Unix seconds.
webhook-signatureThe signature, as v1,<base64>. See Verify signatures.
Body
{
  "type": "build.failed",
  "timestamp": "2026-10-04T10:14:00Z",
  "data": {
    "project": { "id": "0199…", "name": "Nine Lives" },
    "build": {
      "id": "0199…",
      "number": 283,
      "status": "failed",
      "url": "https://app.unrealcloud.io/studios/…/builds/0199…",
      "startedAt": "2026-10-04T10:02:18Z",
      "finishedAt": "2026-10-04T10:14:00Z",
      "durationSeconds": 702
    },
    "preset": {
      "id": "0199…",
      "name": "Windows Shipping",
      "platform": "win64",
      "branch": "main"
    },
    "commit": {
      "sha": "a41c9e0b7d52c1f0e8a3b6d4c9f2e1a7b5c3d8e0",
      "message": "Add door interaction"
    }
  }
}

type is one of:

TypeSent when
build.runningA build starts running. finishedAt is null and durationSeconds is 0.
build.failedA build fails.
build.passedA build passes.
testYou click Send test. data only has project and a message.

commit is left out when the build has no commit.

Responding

Return any 2xx status within 10 seconds. Anything else, a timeout, or a redirect counts as failed.

Deliveries aren't retried. Each one is listed under the webhook's Recent deliveries with the request sent and your server's response (the first 256 KB of the body), or the error.

Verify signatures

Webhooks are signed with the Standard Webhooks spec, so you can verify them with one of its libraries:

Node.js
import { Webhook } from 'standardwebhooks';

const webhook = new Webhook(process.env.UNREAL_CLOUD_WEBHOOK_SECRET);

// Throws if the signature is invalid or the timestamp is too old
const event = webhook.verify(rawBody, headers);

Always verify against the raw request body, before parsing it as JSON.

To verify by hand, compute an HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the base64-decoded part of the secret after whsec_, then compare its base64 encoding to the signature after v1,:

Node.js, without a library
import crypto from 'node:crypto';

function verify(secret: string, headers: Record<string, string>, rawBody: string) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 5 * 60) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const digest = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64');
  const expected = Buffer.from(`v1,${digest}`);

  return headers['webhook-signature'].split(' ').some((signature) => {
    const actual = Buffer.from(signature);
    return actual.length === expected.length && crypto.timingSafeEqual(actual, expected);
  });
}

Treat the signing secret like a password. If it leaks, remove the webhook and add it again to get a new secret.

On this page