Webhooks
Receive real-time event notifications from Salesly via webhooks.
Salesly can send webhook events to your server when certain actions occur in your account.
Supported Events
| Event | Trigger |
|---|---|
order.finished | An order reaches the "finished" state (state 6) |
opportunity.finished | An opportunity reaches a final state (state = 4 won or state = 5 lost) |
opportunity.won | An opportunity is marked as won (state = 4) |
opportunity.lost | An opportunity is marked as lost (state = 5) |
When an opportunity is closed, Salesly currently sends the generic opportunity.finished event and also the specific outcome event (opportunity.won or opportunity.lost).
Configuration
Configure your webhook endpoint in Settings → Integrations:
- Webhook URL — the HTTPS endpoint that will receive events
- Webhook Secret — used to sign payloads for verification
Payload Format
{
"event": "opportunity.won",
"data": {
"opportunity_id": 123,
"name": "Website redesign",
"enterprise_id": 456,
"state": 4
},
"timestamp": "2026-03-05T14:30:00+00:00"
}opportunity.finished is still emitted for backward compatibility when an opportunity closes. Salesly now also emits opportunity.won for state = 4 and opportunity.lost for state = 5.
order.finished uses the same envelope, but its data object looks like:
{
"order_id": 987,
"reference": "ORD-00042",
"enterprise_id": 456,
"state": 6
}Signature Verification
Every webhook includes an X-Salesly-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret.
Always verify signatures before processing webhook payloads.
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js example
app.post("/webhooks/salesly", (req, res) => {
const signature = req.headers["x-salesly-signature"];
const body = JSON.stringify(req.body);
if (!verifyWebhook(body, signature, process.env.SALESLY_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
res.status(200).send("OK");
});Headers
| Header | Description |
|---|---|
X-Salesly-Signature | HMAC-SHA256 signature of the request body |
X-Salesly-Event | The event type (for example order.finished or opportunity.won) |
Content-Type | application/json |
Best Practices
- Always verify signatures — never process unverified payloads
- Respond quickly — return
200 OKimmediately, then process asynchronously - Handle duplicates — deduplicate using
eventplus the resource identifier insidedata - Use HTTPS — webhook URLs must use HTTPS in production