Webhooks
Webhooks provide a way for notifications to be delivered to an external web server whenever certain events occur in SYNQ.
SYNQ supports webhooks for a variety of events.
Enable Webhooks
To enable webhooks:
- Sign into MyStore as a Location Administrator
- Select Location Config
- Scroll down to Webhooks
- Set the Webhook URL setting to the URL of your application that will receive the webhook request
- Enable webhooks for the desired applications:
- Call for Help
- Curbside
- Click Update Location Settings
Event Payloads
SYNQ provides the following webhook event payloads as application/json
data.
Call for Help
{
"type": "callforhelp",
"created_at": "2024-11-13T17:42:44Z",
"status": "created|escalated|claimed|on_the_way|completed|timed_out|cancelled",
"details": {
"type": "display|call_button",
"name": "Call button 1",
"option_text": "Request to speak to a manager",
"option_prompt": "A customer would like to speak with a manager",
},
"internal_location_id": 1,
"internal_id": 999,
"store_number": "001",
"message": "A new Call for Help Request has been created.",
"uuid": "00000000-0000-0000-0000-000000000000"
}
Curbside
{
"type": "curbside",
"created_at": "2024-11-13T17:42:44Z",
"status": "created|claimed|completed|unreviewed_escalation|undelivered_escalation",
"details": {
"order_number": "1234",
"order_pin": "4321",
},
"internal_location_id": 1,
"internal_id": 999,
"store_number": "001",
"message": "A Curbside Customer has arrived to pick up Order <order_number> at <pickup_zone>"
}
OrderUp
Coming soon!
{
"type": "orderup",
"created_at": "2024-11-13T17:42:44Z",
"status": "created|reviewed|claimed|picking|fulfilled|completed|cancelled",
"details": {
"order_number": "1234",
"order_uid": "4321",
"customer_number": "9876",
},
"internal_location_id": 1,
"internal_id": 999,
"store_number": "001",
"message": "A Picking Utility Order has been created (Order#: <order_number>)"
}
Radio
{
"type": "radio",
"created_at": "2024-11-13T17:42:44Z",
"status": "speech-detected",
"details": {
"edge_id": "6803a571-8904-441d-b2ec-6886e9307635",
"text": "Cleanup on aisle 4.",
}
}
Validating webhook deliveries
You can use a webhook Verification Token to verify that a webhook delivery came from SYNQ and has not been tampered with.
When enabled, each webhook request is signed with your secret verification token. The server that receives the webhook request can then use the same secret verification token to confirm the authenticity of the request.
Enable Signed Webhook Requests
To enable signed webhook requests:
- Sign into MyStore as a Location Administrator
- Select Location Config
- Scroll down to Webhooks
- Provide a strong secret value for the the Verification Token
- Click Update Location Settings
Validate Signed Webhook Requests
To verify the signature of a webhook request received by your application:
- Securely make the Verification Token secret value available to your server application.
- For example, using a environment variable or a key vault.
- In your request pipeline extract the
x-signature
andx-timestamp
headers.x-timestamp
represents the time of the request in the number of seconds since the Unix Epoch (January 1, 1970 GMT) .x-signature
represents the SHA256 HMAC of thex-timestamp
value + the raw request body.
- Verify that the
x-timestamp
matches the current time (within an acceptable tolerance). - Verify that the SHA256 HMAC of the
x-timestamp
combined with the received raw request body matches thex-signature
.
The following is an example implementation that validates a signed request in a NodeJS Express application.
import { createHmac, timingSafeEqual } from "crypto";
import express, { json } from "express";
const app = express();
app.use(json({
verify: (req, res, buf, encoding) => {
const currentTime = Date.now() / 1000;
const maxTimeDifference = 60;
const secretKey = process.env.secretKey;
if (!secretKey) {
throw new Error("Missing secretKey");
}
const signature = req.headers["x-signature"] as string;
if (!signature) {
throw new Error("Missing signature");
}
const timestamp = req.headers["x-timestamp"];
if (!timestamp) {
throw new Error("Missing timestamp");
}
if (Math.abs(currentTime - parseInt(timestamp)) > maxTimeDifference) {
throw new Error("Expired timestamp");
}
const body = buf.toString();
const message = timestamp + body;
const serverSignature = createHmac("sha256", secretKey)
.update(message)
.digest("hex");
if (timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(serverSignature, "hex"))
) {
console.log("Valid signature");
} else {
throw new Error("Invalid signature");
}
},
}));