SparrowDesk

Security & Verification

Verify webhook signatures and secure your SparrowDesk webhook endpoint

Webhook deliveries push workspace data — potentially including personal data — to an endpoint you control. This guide covers how SparrowDesk secures deliveries in transit, how to verify that a request genuinely came from SparrowDesk, and what you're responsible for on the receiving side.

Transport security

  • All deliveries are sent over HTTPS (TLS). Plain HTTP endpoints are rejected at configuration time, including for testing.
  • SparrowDesk validates your endpoint's TLS certificate. Endpoints with invalid certificates will fail delivery.
  • Deliveries to private, loopback, link-local, and internal IP ranges (for example RFC 1918 addresses, 127.0.0.0/8, 169.254.0.0/16, and cloud metadata endpoints) are blocked.

Verifying signatures

Every delivery carries an HMAC-SHA256 signature computed over the raw request body using your target's signing secret. Verifying it proves the request originated from SparrowDesk and was not tampered with.

To verify a delivery:

  1. Read the raw request body before any JSON parsing. The signature is computed over the exact bytes sent — re-serializing parsed JSON will produce a different string and the verification will fail.
  2. Compute an HMAC-SHA256 digest of the raw body using your signing secret.
  3. Compare your digest to the value of the X-SparrowDesk-Signature header using a constant-time comparison. Never use a plain string equality check — it leaks timing information.
  4. Reject the request if the signatures don't match.

Example: Node.js

const crypto = require("crypto");

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");

  const expectedBuffer = Buffer.from(expected, "hex");
  const receivedBuffer = Buffer.from(signatureHeader, "hex");

  return (
    expectedBuffer.length === receivedBuffer.length &&
    crypto.timingSafeEqual(expectedBuffer, receivedBuffer)
  );
}

If you use Express, make sure you have access to the raw body — for example:

const express = require("express");
const app = express();

app.post(
  "/webhooks/sparrowdesk",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("X-SparrowDesk-Signature");

    if (!verifySignature(req.body, signature, process.env.SPARROWDESK_WEBHOOK_SECRET)) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(req.body);
    // Acknowledge quickly, then process asynchronously
    res.status(200).send("OK");
  }
);

Rejecting replays

The event timestamp is part of the signed body, and each delivery also carries a X-SparrowDesk-Timestamp header. After verifying the signature, check that the timestamp is within an acceptable window (for example, 5 minutes) and reject requests outside it. This prevents an attacker from replaying a previously captured, validly-signed request.

Managing signing secrets

  • Each webhook target has its own signing secret — there is no shared workspace-wide secret. If one endpoint is compromised, other targets are unaffected.
  • The secret is shown in full only once, at creation or regeneration. After that, it is masked in the UI.
  • You can regenerate a target's secret at any time from Settings → Webhooks. Rotation supports a grace period of roughly 24 hours during which both the old and new secrets are valid, so you can roll out the new secret to your endpoint without dropping events. During the grace period, verify against either secret.
  • Store the secret like any other credential: in environment variables or a secrets manager, never in version control.

Payloads and personal data

  • Webhook payloads may contain personal data (names, email addresses, phone numbers, message contents). Securing the receiving endpoint — and everything downstream of it — is your responsibility.
  • Deletion events (conversation.deleted, contact.deleted) intentionally carry a minimal identifying payload (IDs only), not the full resource, to limit exposure of data after a record is gone.
  • Payload contents are fixed per event type and cannot be expanded, which bounds the data that can leave SparrowDesk through a webhook.

Best Practices

  1. Always verify signatures: Reject any request that fails verification before doing any processing.

  2. Check timestamps: Reject requests older than your replay window even when the signature is valid.

  3. Acknowledge fast, process async: Return 2xx immediately and queue the event for processing, so slow handlers don't cause retries.

  4. De-duplicate on event id: Deliveries are at-least-once; make your handler idempotent.

  5. Restrict who can manage webhooks: The manage_webhooks permission controls creating targets, viewing secrets, and redelivering events — grant it deliberately.

Next Steps

Need Help?

If you have questions or run into issues, contact us at [email protected].