Webhooks
Opem posts an event to your server when something happens in a project. Add an endpoint, an https address, on the Webhooks tab of the project's Configuration page, on Professional and Enterprise, and choose its events.
Events
Each delivery's body is JSON: the event's id, type, createdAt, projectId and data. The same event sent to two endpoints shares its id, so you can ignore one you have seen.
| Event | When | Data |
|---|---|---|
| request.created | A request is raised in the widget, a hand-off from the assistant included. | request |
| request.replied | Your team replies: in the dashboard, through the management API, or by replying to Opem's email. | request, message (the reply and its channel: dashboard, api or email) |
| request.closed | A request is closed: in the dashboard, with a reply sent with Close, or through the management API. | request, closedBy (staff or api) |
| answer.given | The assistant answers in the widget with some text. | answer (who asked, the question, the answer, whether it read a document) |
| answer.handed_over | A person hands an answer over to your team, which raises a request too. | answer.id, request |
Verify each delivery
Every delivery is signed with the endpoint's secret, whsec_…, shown once when the endpoint is made or its secret rotated. In Node.js, verifyWebhook() from @opem/node checks the signature and the time, and returns the event, typed.
import { verifyWebhook } from "@opem/node";
export async function POST(request: Request) {
const event = verifyWebhook({
body: await request.text(),
headers: request.headers,
secret: process.env.OPEM_WEBHOOK_SECRET!, // whsec_…
});
if (event.type === "request.created") {
console.log(event.data.request.reference);
}
return new Response(null, { status: 204 });
}In any other language: take the raw body exactly as received and the Opem-Timestamp header, and compute HMAC-SHA256 over the timestamp, a full stop and the body, with the whole secret string, whsec_ included, as the key. Compare its lowercase hex with the value after v1= in Opem-Signature, in constant time, and refuse a timestamp more than 5 minutes from your clock.
| Header | Value |
|---|---|
| Opem-Signature | v1= and the lowercase hex HMAC-SHA256 of the signed string, with the endpoint's secret |
| Opem-Timestamp | when Opem signed this attempt, in unix seconds |
| Opem-Delivery | the delivery's id: the same on every attempt, new on a redelivery |
| Opem-Event | the event, as in the body's type |
Deliveries and retries
A delivery is a POST with Content-Type: application/json. Any 2xx within 10 seconds counts as delivered. Anything else, a redirect included, is tried again after 1 minute, 10 minutes, 1 hour, 6 hours, 16 hours, each attempt signed afresh. After that the delivery is marked as not delivered; the Webhooks tab lists every delivery for 30 days, and your team can send one again from there.
Redirects are never followed, and Opem only delivers to public https addresses.