Webhooks let your system react when something changes in SMP — a new project is created, a tester completes a task, a video transcription finishes. This guide walks through the minimum viable setup: a single endpoint that logs events, plus a webhook configured against it.
What you'll build
By the end of this guide you'll have:
- An HTTP endpoint that accepts POST requests from SMP.
- A registered webhook in your SMP organisation that fires on
project.completed. - A signed-payload verification step so only legitimate events are processed.
1. Stand up the endpoint
Any HTTPS endpoint that accepts a POST request works. The minimum your server needs to do is read the body, verify the signature, and return 200.
A bare-minimum Node + Express handler:
import express from 'express'
import crypto from 'node:crypto'
const app = express()
app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf } }))
app.post('/hooks/smp', (req, res) => {
const signature = req.header('X-SMP-Signature')
if (!verifySignature((req as any).rawBody, signature)) {
return res.status(401).send('invalid signature')
}
console.log('SMP event:', req.body.type, req.body.data)
res.sendStatus(200)
})
function verifySignature(body: Buffer, signature: string | undefined): boolean {
if (!signature) return false
const expected = crypto
.createHmac('sha256', process.env.SMP_WEBHOOK_SECRET!)
.update(body)
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}That's it for the server. The signature header is what makes the endpoint safe to expose publicly — without it, anyone could POST junk and your handler would treat it as legitimate.
2. Register the webhook
Once the endpoint is reachable from the public internet, register it in the SMP admin portal:
- Open Organisation settings → Webhooks.
- Click New webhook.
- Set the URL to your endpoint (
https://example.com/hooks/smp). - Pick the events you want — start with
project.completedfor this guide. - Generate a signing secret and copy it into your server's
SMP_WEBHOOK_SECRETenv var. - Save.
SMP fires a synthetic webhook.test event right after you save, so you'll see one event hit your endpoint immediately. If your server doesn't log it, double-check the URL and that it's reachable over HTTPS (no self-signed certs).
3. What you'll receive
Every event has the same envelope:
{
"id": "evt_01HXY3K6Q4SC3D6EA8JVQA0Z2W",
"type": "project.completed",
"createdAt": "2026-05-02T14:32:11Z",
"data": {
"projectId": "proj_7H8K2",
"name": "Authentication accessibility audit",
"completedAt": "2026-05-02T14:32:09Z"
}
}The data shape is event-specific. The full taxonomy of event types and their payloads lives in the API reference (linked at the bottom of this page).
Common pitfalls
- HTTPS only. SMP refuses
http://URLs. Use a tunnelling tool (ngrok, Cloudflare Tunnel) for local development. - Body parsing eats the raw bytes. If you parse JSON before computing the signature, the bytes you hash won't match what SMP signed. Capture the raw body before parsing — see
verifycallback in the example above. - 5-second timeout. SMP gives your endpoint 5 seconds to respond
2xx. Anything heavier (database writes, downstream API calls) should be queued and processed asynchronously.
What's next
- Add more event types. Browse the full list in the API reference and subscribe to whatever your workflow needs.
- Set up retry monitoring. SMP exposes a
webhook.delivery_failedadmin event you can route to your alerting. - Consider a queue. For anything beyond logging, push events into your own queue (SQS, Pub/Sub, Redis) before processing — keeps your endpoint fast and resilient to downstream outages.