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 |
| A new ticket was created. |
| A ticket’s details were modified. |
| A ticket’s status changed (e.g. open → closed). |
| A comment was added to a ticket. |
| 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
Go to Company Settings → API → Webhooks.
Click New Webhook.
Enter the URL where Visibuild should send events (must be HTTPS).
Select the events you want to subscribe to.
Optionally add a description (e.g. "Salesforce ticket sync").
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 |
| HMAC-SHA256 signature of the payload. |
| The event type (e.g. |
| Unique delivery ID – use for idempotency. |
| 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
Extract the
X-Visibuild-TimestampandX-Visibuild-Signatureheaders.Concatenate the timestamp, a period (
.), and the raw request body.Compute the HMAC-SHA256 of that string using your webhook secret.
Compare your computed signature with the one in the header.
Reject the request if the signatures don't match.
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 |
| List all webhook endpoints. |
| Create a new endpoint. |
| Get details for a specific endpoint. |
| Update an endpoint (URL, events, enabled). |
| 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 |
Duplicate events received | Normal – retries can cause duplicates. | Use the |


