Webhooks
SpeedyFiles can POST a JSON payload to your URL whenever a package event happens. HMAC-SHA256 signed for verification.
Setup
- Log in as admin → Settings → Webhooks
- Enter the receiving URL (your endpoint)
- Pick the events you want — defaults to all
- Submit. A signing secret is generated and shown once; save it.
Events
| Event | Fired when |
|---|---|
package.created | An internal user creates a new package (outbound or inbound) |
package.finalized | An outbound package is sealed and the magic-link email goes out |
package.file_uploaded | A file is added (by owner or recipient) |
package.downloaded | The recipient downloads any file from the package |
package.revoked | The owner / admin revokes the link |
package.deleted | The owner / admin permanently deletes |
package.expired | Package TTL elapsed (fired by background job) |
Payload format
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: SpeedyFiles-Webhook/1.0
X-SpeedyFiles-Event: package.finalized
X-SpeedyFiles-Signature: sha256=<hex digest>
X-SpeedyFiles-Hook-Id: 42
{
"event": "package.finalized",
"timestamp": "2026-05-13T20:30:22Z",
"package_id": "abc123",
"title": "Q4 financials",
"recipient_email": "cfo@acme.com",
"owner_user_id": 1,
"file_count": 3,
"expires_at": "2026-05-27T20:30:22"
}
Verifying the signature
HMAC-SHA256 over the raw request body, keyed by the per-subscription secret.
Python
import hashlib, hmac
def verify(body: bytes, header: str, secret: str) -> bool:
if not header.startswith("sha256="):
return False
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header.removeprefix("sha256="))
Node.js
import crypto from 'crypto';
function verify(body, header, secret) {
if (!header.startsWith('sha256=')) return false;
const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header.slice(7)));
}
Bash (sanity check)
BODY=$(cat /tmp/last_webhook_body.json)
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
echo "Expected: $SIG"
# Compare to value in X-SpeedyFiles-Signature header
Reliability & retries
- Each POST has an 8-second timeout
- HTTP 2xx = success
- Anything else (3xx-5xx, timeout, connection refused) counts as a failure
- After 10 consecutive failures, the subscription auto-disables
- Click Enable in the admin UI to re-arm after fixing
- No retry queue at v1 — your endpoint should be idempotent and tolerant to brief unavailability
Receiving examples
Slack notifications via Bolt
import json, requests, hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = "whsec_yoursecrethere"
SLACK_WEBHOOK = "https://hooks.slack.com/services/T.../B.../..."
@app.post('/sf-webhook')
def hook():
sig = request.headers.get('X-SpeedyFiles-Signature', '')
expected = "sha256=" + hmac.new(SECRET.encode(), request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
evt = request.json
if evt['event'] == 'package.downloaded':
requests.post(SLACK_WEBHOOK, json={
"text": f"📥 {evt['recipient_email']} downloaded {evt['title']}"
})
return "", 204
Test send
In the admin UI, each webhook row has a Test button that fires a synthetic webhook.test event. Use it to confirm your receiver works without waiting for a real package event.