Skip to content

Webhooks

Instead of asking the API “anything new?” every few minutes, let Sevastack tell you. When something happens we send an HTTPS POST to your URL within seconds. It works with your own server, Zapier (“Webhooks by Zapier → Catch Hook”), Make (“Custom webhook”), n8n, Pabbly or Google Apps Script.

Set up

  1. Open Settings → Integrations → Webhooks and choose Add URL (Scale plan; settings permission).
  2. Paste your HTTPS URL and tick the events you want.
  3. Copy the signing secret (whsec_…). It is shown once.
  4. Press Send test. You'll see the response code your server returned.

Your URL must use https:// and be reachable on the public internet. Up to 10 URLs per organisation.

Events

EventSent when
volunteer.createdSomeone signs up (website, widget, portal invite, API) or staff add a volunteer.
registration.createdA volunteer registers for an activity, or joins its waitlist.
registration.cancelledA volunteer or staff cancels a registration.
waitlist.promotedA spot opened and the longest-waiting volunteer was confirmed.
attendance.markedA volunteer is marked present (staff, QR check-in or API).
hours.loggedHours are logged by staff, by the volunteer, or through the API.
webhook.testYou press Send test.

What you receive

A POST with a JSON body and these headers:

Sevastack-EventThe event name, e.g. hours.logged.
Sevastack-DeliveryUnique id of this delivery. The same id is resent on retries, so use it to ignore duplicates.
Sevastack-Signaturet=<unix seconds>,v1=<hex>. See “Check the signature”.
volunteer.created
{
  "id": "cmv4…",                       // delivery id
  "event": "volunteer.created",
  "created_at": "2026-10-11T08:30:00.000Z",
  "organization_id": "cmnd…",
  "data": {
    "via": "website_widget",           // dashboard, public_form, website_widget, referral, api
    "volunteer": { "id": "cmv3…", "name": "Priya Sharma", "email": "priya@example.com", "phone": "9876543210",
                   "status": "ONBOARDED", "employer": "Infosys", "total_hours": 0, "...": "same fields as the REST API" }
  }
}
registration.created / registration.cancelled / waitlist.promoted / attendance.marked
{
  "id": "cmv4…", "event": "attendance.marked", "created_at": "…", "organization_id": "cmnd…",
  "data": {
    "registration": {
      "id": "cmv3…", "volunteer_id": "cmv3…", "status": "CONFIRMED", "attended": true,
      "checked_in_at": "2026-10-11T04:05:00.000Z",
      "activity": { "id": "cmv2…", "title": "Tree plantation drive", "start_date": "2026-10-11" }
    }
  }
}
hours.logged
{
  "id": "cmv4…", "event": "hours.logged", "created_at": "…", "organization_id": "cmnd…",
  "data": {
    "logged_by": "staff",              // staff, volunteer, api, attendance
    "entries": [{ "id": "cmv4…", "volunteer_id": "cmv3…", "date": "2026-10-11", "hours": 3.5 }]
  }
}

Marking attendance also logs the activity's hours, so it sends both attendance.marked and hours.logged (with logged_by: "attendance").

Respond quickly

Return any 2xx status within 10 seconds. Do slow work after you respond. We don't follow redirects, and we ignore your response body.

Retries

If your URL times out or returns anything other than 2xx, we retry after about 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then mark the delivery failed (you can retry it from Settings). Retries run between 8:30 am and 10:30 pm IST. Events can arrive more than once and occasionally out of order: de-duplicate on Sevastack-Delivery and use created_at to order them. After 20 failures in a row the URL is switched off and Settings shows why. Switch it back on once it's fixed.

Check the signature

Anyone can send a request to your URL, so check that it came from Sevastack. Compute an HMAC-SHA256 of <t>.<raw request body> with your signing secret and compare it with v1. Use the raw body exactly as received, before parsing the JSON. Reject requests whose t is more than 5 minutes old.

Node.js (Express)
import crypto from "crypto";
import express from "express";
const app = express();

app.post("/sevastack-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Sevastack-Signature") || "";
  const t = /t=(\d+)/.exec(header)?.[1];
  const v1 = /v1=([0-9a-f]+)/.exec(header)?.[1];
  const expected = crypto.createHmac("sha256", process.env.SEVASTACK_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  const valid = v1 && v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
  if (!t || !fresh || !valid) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body);
  // ...save event.data, skipping event.id if you've seen it before
  res.sendStatus(200);
});
PHP
$body   = file_get_contents("php://input");
$header = $_SERVER["HTTP_SEVASTACK_SIGNATURE"] ?? "";
preg_match('/t=(\d+)/', $header, $t);
preg_match('/v1=([0-9a-f]+)/', $header, $v1);
$expected = hash_hmac("sha256", $t[1] . "." . $body, getenv("SEVASTACK_WEBHOOK_SECRET"));
if (empty($t) || empty($v1) || abs(time() - (int)$t[1]) > 300 || !hash_equals($expected, $v1[1])) {
  http_response_code(400); exit;
}
$event = json_decode($body, true);
http_response_code(200);
Python (Flask)
import hmac, hashlib, os, re, time
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/sevastack-webhook")
def hook():
    body = request.get_data()
    header = request.headers.get("Sevastack-Signature", "")
    t = re.search(r"t=(\d+)", header); v1 = re.search(r"v1=([0-9a-f]+)", header)
    if not t or not v1 or abs(time.time() - int(t.group(1))) > 300:
        abort(400)
    expected = hmac.new(os.environ["SEVASTACK_WEBHOOK_SECRET"].encode(),
                        t.group(1).encode() + b"." + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, v1.group(1)):
        abort(400)
    event = request.get_json()
    return "", 200

Using Zapier, Make or Google Sheets without code? You can skip the signature check, but then keep the URL private: anyone who has it can post fake events to it.

Security notes

  • Events contain volunteers' names and contact details. Send them only to systems your organisation controls.
  • Make a new secret from Settings at any time; the old one stops working immediately.
  • Removing a webhook stops all deliveries to it straight away.