Receive real-time HTTP callbacks when events occur in your Jarvis SDK account. Available on Pro, Business, and Enterprise plans.
Register a webhook endpoint via the API or Dashboard. Each webhook subscription listens for specific event types and delivers payloads to your URL.
cURL
curl -X POST https://jarvissdk.com/api/v1/webhooks \
-H "x-api-key: jsk_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/jarvis",
"events": ["execution.completed", "execution.failed"],
"secret": "whsec_your_signing_secret"
}'TypeScript
import { JarvisSDK } from "@jarvis-sdk/client";
const sdk = new JarvisSDK({ apiKey: "jsk_your_key" });
const webhook = await sdk.webhooks.create({
url: "https://your-app.com/webhooks/jarvis",
events: ["execution.completed", "execution.failed"],
secret: "whsec_your_signing_secret",
});
console.log("Webhook ID:", webhook.id);Python
import requests
resp = requests.post("https://jarvissdk.com/api/v1/webhooks",
headers={"x-api-key": "jsk_your_key"},
json={
"url": "https://your-app.com/webhooks/jarvis",
"events": ["execution.completed", "execution.failed"],
"secret": "whsec_your_signing_secret"
})
webhook = resp.json()
print("Webhook ID:", webhook["id"])Subscribe to specific events or use * for all events.
| Event | Description | When |
|---|---|---|
| execution.completed | Module action succeeded | After every successful execute call |
| execution.failed | Module action failed | On execution error (timeout, bad input, runtime crash) |
| module.installed | Module installed by your tenant | First use or explicit install |
| module.uninstalled | Module removed from tenant | Via API or dashboard uninstall |
| certification.completed | Certification run finished | After manual or scheduled cert check |
| certification.tier_changed | Module certification tier changed | When tier upgrades/downgrades (e.g., bronze to silver) |
| billing.limit_approaching | Usage at 80% of plan limit | Checked on each execution |
| billing.limit_exceeded | Plan execution limit reached | When quota is exhausted |
| billing.invoice_created | New invoice generated | End of billing cycle or overage |
| trust.score_changed | Trust score changed significantly | After cert run or community feedback |
| workflow.completed | Multi-step workflow finished | All workflow steps completed |
| workflow.failed | Workflow step failed | When a workflow step errors out |
All webhook payloads follow a consistent envelope. The data field varies by event type.
execution.completed payload
{
"id": "evt_abc123",
"event": "execution.completed",
"timestamp": "2026-03-20T15:30:00Z",
"data": {
"execution_id": "exec_xyz789",
"module": "text-toolkit",
"action": "slugify",
"status": "success",
"latency_ms": 12,
"request_id": "req_abc",
"tenant_id": "335619a3-..."
}
}certification.completed payload
{
"id": "evt_def456",
"event": "certification.completed",
"timestamp": "2026-03-20T16:00:00Z",
"data": {
"module": "hash-toolkit",
"tier": "silver",
"previous_tier": "bronze",
"trust_score": 87,
"checks_passed": 12,
"checks_total": 14,
"report_url": "/api/v1/certification/hash-toolkit/report"
}
}Every delivery includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify signatures before processing payloads to prevent spoofing.
TypeScript (Node.js)
import crypto from "crypto";
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express handler
app.post("/webhooks/jarvis", (req, res) => {
const isValid = verifyWebhook(
req.rawBody,
req.headers["x-webhook-signature"] as string,
"whsec_your_secret"
);
if (!isValid) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.rawBody);
console.log("Event:", event.event, event.data);
res.status(200).send("OK");
});Python (Flask)
import hmac, hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route("/webhooks/jarvis", methods=["POST"])
def handle_webhook():
sig = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(request.data, sig, "whsec_your_secret"):
return "Invalid signature", 401
event = request.get_json()
print(f"Event: {event['event']}", event["data"])
return "OK", 200| Attempt | Delay | Notes |
|---|---|---|
| 1st | Immediate | Original delivery |
| 2nd | 10 seconds | First retry |
| 3rd | 60 seconds | Second retry |
| 4th | 5 minutes | Final retry — webhook disabled after failure |
Success criteria: Your endpoint must return a 2xx status code within 10 seconds. Any other response triggers a retry.
Idempotency: Use the id field to deduplicate. The same event may be delivered more than once across retries.
Auto-disable: After 10 consecutive failed deliveries, the webhook is automatically disabled. Re-enable via API or dashboard.
Full CRUD for webhook subscriptions. All endpoints require x-api-key header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/webhooks | List all webhook subscriptions |
| POST | /api/v1/webhooks | Create a new webhook subscription |
| GET | /api/v1/webhooks/{id} | Get webhook details and delivery history |
| PUT | /api/v1/webhooks/{id} | Update webhook URL, events, or secret |
| DELETE | /api/v1/webhooks/{id} | Delete a webhook subscription |
| POST | /api/v1/webhooks/{id}/test | Send a test event to verify your endpoint |
| POST | /api/v1/webhooks/{id}/enable | Re-enable a disabled webhook |
Next.js App Router
// app/api/webhooks/jarvis/route.ts
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
const SECRET = process.env.JARVIS_WEBHOOK_SECRET!;
export async function POST(req: NextRequest) {
const body = await req.text();
const sig = req.headers.get("x-webhook-signature") ?? "";
const expected = crypto
.createHmac("sha256", SECRET)
.update(body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const event = JSON.parse(body);
switch (event.event) {
case "execution.completed":
console.log("Module executed:", event.data.module, event.data.action);
break;
case "billing.limit_approaching":
// Send Slack alert
break;
case "trust.score_changed":
// Update internal dashboard
break;
}
return NextResponse.json({ received: true });
}Python (FastAPI)
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, json
app = FastAPI()
SECRET = "whsec_your_secret"
@app.post("/webhooks/jarvis")
async def handle_webhook(request: Request):
body = await request.body()
sig = request.headers.get("x-webhook-signature", "")
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
raise HTTPException(status_code=401, detail="Invalid signature")
event = json.loads(body)
if event["event"] == "execution.completed":
print(f"Module: {event['data']['module']}, Latency: {event['data']['latency_ms']}ms")
elif event["event"] == "billing.limit_approaching":
# Trigger alert
pass
return {"received": True}| Symptom | Cause | Fix |
|---|---|---|
| Not receiving any events | Webhook disabled or wrong event types | Check GET /api/v1/webhooks — verify status is 'active' and events list matches |
| Signature verification fails | Secret mismatch or body parsing alters raw payload | Verify you're comparing against the raw request body (not parsed JSON). Check secret matches. |
| Events arrive late | Your endpoint returned non-2xx, triggering retries | Check server logs. Ensure 2xx response within 10s. Use POST /webhooks/{id}/test to verify. |
| Duplicate events received | Normal — retries can deliver the same event twice | Deduplicate using the event 'id' field. Store processed IDs for 24 hours. |
| Webhook auto-disabled | 10 consecutive delivery failures | Fix your endpoint, then POST /api/v1/webhooks/{id}/enable to re-activate. |
| Missing events for specific modules | Event filter is too narrow | Update webhook to include needed event types, or use '*' for all events. |
Return 200 immediately — Process events asynchronously (e.g., queue them). Your handler must respond within 10 seconds.
Always verify signatures — Never skip HMAC verification, even in development. Spoofed webhooks can trigger unintended actions.
Use the test endpoint — Send POST /api/v1/webhooks/{id}/test to verify your handler works before subscribing to real events.
Monitor delivery health — Check GET /api/v1/webhooks/{id} for delivery statistics including success rate and average latency.
Can I subscribe to events from specific modules only?
Not yet. Webhooks fire for all modules in your tenant. Filter by module name in your handler. Module-level filtering is on the roadmap.
What happens if my endpoint is down for an extended period?
After 10 consecutive failures, the webhook is auto-disabled. Events during downtime are not queued — they are lost. Re-enable the webhook when your endpoint is back up.
Is there a maximum payload size?
Webhook payloads are capped at 64KB. Executions with large outputs include a truncated summary and a link to fetch the full result.
Can I use webhooks on the Free plan?
No. Webhooks are available on Pro ($29/mo), Business ($299/mo), and Enterprise plans.