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:

  1. Open Organisation settings → Webhooks.
  2. Click New webhook.
  3. Set the URL to your endpoint (https://example.com/hooks/smp).
  4. Pick the events you want — start with project.completed for this guide.
  5. Generate a signing secret and copy it into your server's SMP_WEBHOOK_SECRET env var.
  6. 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 verify callback 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_failed admin 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.