Skip to main content

Webhooks

Written by Hugh Evans
Updated this week

Webhooks let you receive real-time notifications when things happen in Visibuild such as a ticket being created, a status changing, or a comment being added. Instead of polling the API repeatedly, Visibuild sends an HTTP POST request to a URL you specify whenever an event occurs.

This is useful for keeping external systems (like Salesforce, a CRM, or a custom dashboard) in sync with Visibuild without delay.

Supported events

Event

Description

ticket.created

A new ticket was created.

ticket.updated

A ticket’s details were modified.

ticket.status_changed

A ticket’s status changed (e.g. open → closed).

ticket.comment_added

A comment was added to a ticket.

ticket.archived

A ticket was archived.

You choose which events each webhook endpoint listens for as you don’t have to subscribe to all of them.

Setting up a webhook

You can register webhooks either through the Visibuild UI or via the API.

Option A: Using the UI

  1. Go to Company Settings → API → Webhooks.

  2. Click New Webhook.

  3. Enter the URL where Visibuild should send events (must be HTTPS).

  4. Select the events you want to subscribe to.

  5. Optionally add a description (e.g. "Salesforce ticket sync").

  6. Click Create.

Visibuild generates a webhook secret – copy this and store it securely. You’ll need it to verify that incoming requests are genuinely from Visibuild.

Option B: Using the API

POST https://app.visibuild.com.au/api/core/v1/webhooks
Authorization: Bearer {access_token}
Content-Type: application/json

{
"url": "https://your-system.com/webhooks/visibuild",
"events": ["ticket.created", "ticket.status_changed"],
"description": "My integration webhook"
}

The response includes the secret field. Store this securely. This endpoint and more is documented in the Core API documentation.

Webhook payload format

When an event occurs, Visibuild sends a JSON payload like this:

{
"id": "019cda2d-3826-d0fb-c8cc-66bd0ee25062",
"type": "ticket.status_changed",
"timestamp": "2026-03-17T09:00:00Z",
"apiVersion": "2025-01-01",
"data": {
"ticket": {
"id": "f8a1b2c3-d4e5-6789-abcd-ef0123456789",
"ticketNo": "456",
"title": "Kitchen cabinet defect",
"status": "closed",
"previousStatus": "open"
},
"change": {
"from": "open",
"to": "closed",
"changedAt": "2026-03-17T09:00:00Z"
}
}
}

Each event has a unique id that you can use for idempotency – if you receive the same event ID twice, you can safely skip the duplicate.

Security headers

Every webhook request includes headers you should use to verify authenticity:

Header

Purpose

X-Visibuild-Signature

HMAC-SHA256 signature of the payload.

X-Visibuild-Event

The event type (e.g. ticket.status_changed).

X-Visibuild-Delivery

Unique delivery ID – use for idempotency.

X-Visibuild-Timestamp

Unix timestamp of when the event was sent.

Verifying webhook signatures

Always verify the signature before processing a webhook. This ensures the request came from Visibuild and hasn’t been tampered with.

How it works

Visibuild signs each webhook using your endpoint’s secret. The signature is calculated as:

HMAC-SHA256(secret, "{timestamp}.{raw_request_body}")

The result is sent in the X-Visibuild-Signature header, prefixed with sha256=.

Verification steps

  1. Extract the X-Visibuild-Timestamp and X-Visibuild-Signature headers.

  2. Concatenate the timestamp, a period (.), and the raw request body.

  3. Compute the HMAC-SHA256 of that string using your webhook secret.

  4. Compare your computed signature with the one in the header.

  5. Reject the request if the signatures don't match.

  6. Reject the request if the timestamp is more than 5 minutes old (prevents replay attacks).

Example: Node.js

const crypto = require('crypto');

function verifyWebhook(secret, timestamp, body, signature) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}

Example: Ruby

def verify_webhook(secret, timestamp, body, signature)
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
"SHA256", secret, "#{timestamp}.#{body}"
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end

Example: Python

import hmac, hashlib

def verify_webhook(secret, timestamp, body, signature):
expected = "sha256=" + hmac.new(
secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

Retry behaviour

If your endpoint returns a non-2xx response or times out, Visibuild retries with exponential backoff:

Attempt

Delay

1

Immediate

2

30 seconds

3

2 minutes

4

5 minutes

5

15 minutes

6

30 minutes

7

1 hour

8

2 hours

9

4 hours

10

8 hours

After 10 failed attempts, the individual delivery is marked as failed and the endpoint is automatically disabled. You can re-enable it from the webhook management UI or API once you’ve resolved the issue.

Tip: Your endpoint should return a 2xx response within 30 seconds. Do any heavy processing asynchronously – acknowledge the webhook quickly, then process the payload in the background.

Managing webhooks

In the UI

Go to Company Settings → API → Webhooks to:

  • View all registered webhook endpoints and their status (active or disabled).

  • Edit an endpoint’s URL, events, or description.

  • Disable/enable an endpoint.

  • Delete an endpoint you no longer need.

  • View delivery history – see recent deliveries, their status, and response codes.

Via the API

Method

Endpoint

GET /api/core/v1/webhooks

List all webhook endpoints.

POST /api/core/v1/webhooks

Create a new endpoint.

GET /api/core/v1/webhooks/{id}

Get details for a specific endpoint.

PATCH /api/core/v1/webhooks/{id}

Update an endpoint (URL, events, enabled).

DELETE /api/core/v1/webhooks/{id}

Delete an endpoint.

Using webhooks with polling as a fallback

Webhooks are the fastest way to stay in sync, but no delivery mechanism is 100% guaranteed. We recommend combining webhooks with periodic polling as a safety net:

  • Webhooks for real-time updates (covers 99%+ of cases).

  • Polling via GET /api/core/v1/tickets?updatedAfter={timestamp} every 15 minutes to catch anything missed.

  • Nightly reconciliation to verify your system matches Visibuild’s data.

Troubleshooting

Problem

Cause

Solution

Webhook endpoint was automatically disabled

10 consecutive delivery failures.

Check your endpoint is reachable and returning 2xx. Re-enable from the UI.

Signature verification fails

Wrong secret, or body was modified before verification.

Verify you’re using the raw request body (not a parsed/re-serialised version). Check you’re using the correct secret for this endpoint.

Events not arriving

Endpoint not subscribed to that event type.

Check the endpoint’s event subscriptions in the UI or via GET /webhooks/{id}.

Duplicate events received

Normal – retries can cause duplicates.

Use the X-Visibuild-Delivery header or id field for idempotency.

Did this answer your question?