Webhooks
Webhooks allow your application to receive real-time HTTP callbacks when events occur in The One Stack. Instead of polling APIs for changes, register a webhook URL and the platform will POST event payloads to you.
Configuring Webhooks
- Navigate to Hub > Settings > Webhooks.
- Click Add Webhook.
- Enter your endpoint URL (must be HTTPS).
- Select the events you want to subscribe to.
- Copy the webhook secret -- you will need it to verify signatures.
Events
Each product emits events for its core entities. Subscribe to only the events you need.
PSA Events
| Event | Description |
|---|---|
ticket.created | A new ticket was created |
ticket.updated | A ticket was modified (status, priority, assignment, etc.) |
ticket.deleted | A ticket was deleted |
ticket.note_added | A note was added to a ticket |
time_entry.created | A time entry was logged |
time_entry.updated | A time entry was modified |
project.created | A new project was created |
project.status_changed | A project status changed |
sla.breached | An SLA target was missed |
CRM Events
| Event | Description |
|---|---|
contact.created | A new contact was added |
contact.updated | A contact was modified |
company.created | A new company was added |
company.updated | A company was modified |
deal.created | A new deal was created |
deal.stage_changed | A deal moved to a different pipeline stage |
deal.won | A deal was marked as won |
deal.lost | A deal was marked as lost |
activity.completed | An activity (call, meeting, task) was completed |
Books Events
| Event | Description |
|---|---|
invoice.created | A new invoice was generated |
invoice.sent | An invoice was sent to the client |
invoice.paid | An invoice was fully paid |
invoice.overdue | An invoice passed its due date without payment |
payment.received | A payment was recorded |
journal_entry.created | A journal entry was posted |
RMM Events
| Event | Description |
|---|---|
device.added | A new device was enrolled |
device.removed | A device was removed |
alert.triggered | A monitoring alert fired |
alert.resolved | A monitoring alert was resolved |
script.completed | A remote script execution finished |
patch.installed | A patch was successfully installed |
patch.failed | A patch installation failed |
Security Events
| Event | Description |
|---|---|
alert.triggered | A security alert was raised |
alert.escalated | An alert was escalated to a higher severity |
incident.created | A new security incident was opened |
incident.resolved | A security incident was resolved |
scan.completed | A vulnerability scan finished |
vulnerability.found | A new vulnerability was discovered |
Payload Format
All webhook payloads follow a consistent JSON structure:
{
"id": "evt_abc123def456",
"event": "ticket.created",
"timestamp": "2026-03-09T14:30:00.000Z",
"tenantId": "tenant_xyz789",
"product": "psa",
"data": {
"id": "tkt_001",
"title": "Printer not responding",
"status": "open",
"priority": "medium",
"companyId": "comp_456",
"assignedTo": "[email protected]",
"createdAt": "2026-03-09T14:30:00.000Z"
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID for idempotency checks |
event | string | Event type (e.g., ticket.created) |
timestamp | string | ISO 8601 timestamp of when the event occurred |
tenantId | string | The tenant that generated the event |
product | string | The product that emitted the event |
data | object | The full entity payload at the time of the event |
Signature Verification
Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret. Always verify this signature to confirm the payload was sent by The One Stack.
Verification Examples
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post('/webhooks/theonestack', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the event
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
res.status(200).json({ received: true });
});
Python
import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature, expected)
# In your webhook handler (Flask example):
@app.route("/webhooks/theonestack", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Webhook-Signature")
if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
return jsonify({"error": "Invalid signature"}), 401
event = request.json
print(f"Received {event['event']}: {event['data']}")
return jsonify({"received": True}), 200
Retry Policy
If your endpoint does not return a 2xx status code, The One Stack retries delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 10 minutes |
| 3rd retry | 1 hour |
After 3 failed retries, the event is marked as failed. Failed events can be viewed and manually retried from Hub > Settings > Webhooks > Delivery Log.
Best Practices
- Respond quickly. Return a
200response as soon as you receive the payload. Process the event asynchronously if your handling logic is slow. - Use the event ID for idempotency. The same event may be delivered more than once (e.g., during retries). Use
idto deduplicate. - Always verify signatures. Never trust webhook payloads without verifying the
X-Webhook-Signatureheader. - Use HTTPS. Webhook URLs must use HTTPS to ensure payloads are encrypted in transit.
- Monitor your delivery log. Check for failed deliveries regularly in Hub > Settings > Webhooks.