Events & Webhooks
Subscribe to real-time events from SyncBooks and receive webhook notifications when data changes — even when your add-on isn't open.
How Events Work
When something happens in SyncBooks (invoice created, payment received, etc.), an event is dispatched. If your add-on is subscribed, SyncBooks sends a POST request to your webhook URL with the event data.
User creates invoice in SyncBooks
↓
SyncBooks fires "invoice.created" event
↓
Finds all addon subscriptions for this event + org
↓
Sends POST to each subscribed webhookUrl
↓
Your server receives the payload and processes itSubscribing to Events
import { events } from '@syncbooks/addon-sdk'
// Subscribe to specific events
await events.subscribe('invoice.created', 'https://myapp.com/webhooks/syncbooks')
await events.subscribe('invoice.paid', 'https://myapp.com/webhooks/syncbooks')
await events.subscribe('payment.received', 'https://myapp.com/webhooks/syncbooks')
await events.subscribe('customer.created', 'https://myapp.com/webhooks/syncbooks')
// List your current subscriptions
const { data: subs } = await events.subscriptions()
console.log(subs)
// [
// { event: 'invoice.created', webhookUrl: '...', isActive: true },
// { event: 'invoice.paid', webhookUrl: '...', isActive: true },
// ]
// Unsubscribe from an event
await events.unsubscribe('customer.created')
// Get the full event catalog (all available events)
// This is unauthenticated — useful for documentation
// GET /api/addon/v1/events/catalogWhen to subscribe
Typically you subscribe during your addon's first setup (e.g., in a settings page). Subscriptions persist until you unsubscribe or they get disabled due to delivery failures.
Webhook Payload Format
When an event fires, your webhook URL receives a POST with this payload:
// POST https://myapp.com/webhooks/syncbooks
// Headers:
// Content-Type: application/json
// X-SyncBooks-Signature: sha256=a1b2c3d4e5f6...
// X-SyncBooks-Event: invoice.paid
// X-SyncBooks-App: com.mycompany.myapp
// X-SyncBooks-Delivery-Attempt: 1
{
"event": "invoice.paid",
"data": {
"invoiceId": "507f1f77bcf86cd799439011",
"invoiceNumber": "INV-0042",
"customerId": "507f1f77bcf86cd799439012",
"customerName": "Acme Corporation",
"totalAmount": 5825,
"paidAmount": 5825,
"paymentMethod": "bank_transfer"
},
"organizationId": "507f1f77bcf86cd799439013",
"timestamp": "2026-08-13T10:30:00.000Z",
"deliveryId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Handling Webhooks (Server-Side)
You need a server endpoint to receive webhooks. Here's a complete example:
import express from 'express'
import crypto from 'crypto'
const app = express()
app.use(express.json())
// Your addon's app ID (used as signing key)
const APP_ID = 'com.mycompany.myapp'
const SIGNING_KEY = `syncbooks_addon_${APP_ID}`
app.post('/webhooks/syncbooks', (req, res) => {
// ─── Step 1: Verify Signature ────────────────────────────────
const signature = req.headers['x-syncbooks-signature'] as string
const event = req.headers['x-syncbooks-event'] as string
if (!signature) {
return res.status(401).json({ error: 'Missing signature' })
}
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', SIGNING_KEY)
.update(JSON.stringify(req.body))
.digest('hex')
if (signature !== expectedSig) {
console.error('Invalid webhook signature!')
return res.status(401).json({ error: 'Invalid signature' })
}
// ─── Step 2: Process the Event ───────────────────────────────
const { event: eventName, data, organizationId, timestamp } = req.body
console.log(`[${timestamp}] Received: ${eventName} for org ${organizationId}`)
switch (eventName) {
case 'invoice.created':
handleNewInvoice(data, organizationId)
break
case 'invoice.paid':
handleInvoicePaid(data, organizationId)
break
case 'payment.received':
handlePayment(data, organizationId)
break
case 'customer.created':
handleNewCustomer(data, organizationId)
break
default:
console.log('Unhandled event:', eventName)
}
// ─── Step 3: Respond Quickly (within 10 seconds) ─────────────
res.status(200).json({ received: true })
})
// ─── Event Handlers ──────────────────────────────────────────
function handleInvoicePaid(data: any, orgId: string) {
console.log(`Invoice ${data.invoiceNumber} paid! Amount: ${data.totalAmount}`)
// Send thank-you email, update your CRM, sync to external system, etc.
}
function handleNewInvoice(data: any, orgId: string) {
console.log(`New invoice ${data.invoiceNumber} created`)
}
function handlePayment(data: any, orgId: string) {
console.log(`Payment received: ${data.amount}`)
}
function handleNewCustomer(data: any, orgId: string) {
console.log(`New customer: ${data.name}`)
}
app.listen(3001, () => console.log('Webhook server running on :3001'))Verifying Signatures (SDK Helper)
The SDK includes a helper for signature verification:
import { verifyWebhookSignature } from '@syncbooks/addon-sdk'
app.post('/webhooks/syncbooks', async (req, res) => {
const signature = (req.headers['x-syncbooks-signature'] as string).replace('sha256=', '')
const appId = req.headers['x-syncbooks-app'] as string
const signingKey = `syncbooks_addon_${appId}`
const isValid = await verifyWebhookSignature(
JSON.stringify(req.body),
signature,
signingKey
)
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' })
}
// Process event...
res.status(200).json({ received: true })
})Always verify signatures
Without signature verification, anyone could send fake events to your webhook URL. Always verify before processing.
Retry Policy
If your webhook returns a non-2xx status code, SyncBooks retries delivery:
| Attempt | Delay | When |
|---|---|---|
| 1 | Immediate | Right when event fires |
| 2 | +1 minute | If attempt 1 failed |
| 3 | +5 minutes | If attempt 2 failed |
After 3 failed attempts, the subscription is automatically disabled. You'll need to re-subscribe by calling events.subscribe() again.
Keep your webhook fast
Respond within 10 seconds. If you need to do heavy processing, acknowledge immediately (return 200) and process asynchronously using a job queue.
Available Events (35 total)
See the full list in the Events Catalog reference.
Most commonly used:
invoice.createdinvoice.paidinvoice.overduecustomer.createdpayment.receivedexpense.approvedproduct.low_stockstore.order.createdticket.createdNext Steps
- Events Catalog — complete list of all 35 events
- Config & Storage — save webhook URLs and settings per org