Webhooks
Receive real-time HTTP callbacks when proofs complete, fail, or expire.
Webhooks let your application receive real-time notifications when proof-related events occur. Rather than polling the API, Minerva sends an HTTP POST to your endpoint whenever a proof completes, fails, or expires.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/webhooks | Register a new webhook endpoint |
| GET | /api/v1/webhooks | List all registered webhooks for your account |
| GET | /api/v1/webhooks/:id | Get a specific webhook by ID |
| PATCH | /api/v1/webhooks/:id | Update a webhook (URL, events, active state) |
| DELETE | /api/v1/webhooks/:id | Remove a webhook permanently |
Register a Webhook
POST /api/v1/webhooks HTTP/1.1
Host: api.zkesg.com
Authorization: Bearer <your-api-key>
Content-Type: application/json
{
"url": "https://your-app.com/minerva/webhook",
"events": ["proof.completed", "proof.failed"],
"description": "Production proof completion handler"
}| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | HTTPS endpoint to receive events (must be publicly accessible) |
| events | string[] | Yes | Array of event types to subscribe to (see Event Types below) |
| description | string | No | Human-readable notes about the webhook |
| active | boolean | No | Whether the webhook is active (default: true) |
{
"id": "wh_01HX5MQZR9BNT5E2FPMCDVWK4",
"url": "https://your-app.com/minerva/webhook",
"events": ["proof.completed", "proof.failed"],
"secret": "whsec_a7f3c2e9d1b84f6a2e0c5d8b3f7a1e4c",
"active": true,
"created_at": "2026-03-16T12:00:00Z"
}Event Types
| Event | When it fires |
|---|---|
| proof.completed | Proof generation succeeded and the proof is ready |
| proof.failed | Proof generation failed (constraint error, invalid inputs, timeout) |
| proof.expired | A proof older than the retention window was removed |
| proof.verified | A proof was verified via GET /api/v1/proofs/:id/verify |
| key.created | A new API key was created on your account |
| key.revoked | An API key was revoked |
Payload Format
{
"id": "evt_01HX6NRZR9BNT5E2FPMCDVWM5",
"type": "proof.completed",
"created_at": "2026-03-16T14:23:11Z",
"data": {
"proof_id": "proof_01HX4KQZR9BNT5E2FPMCDVWJ3",
"circuit": "carbon-emissions",
"status": "completed",
"proof_blob": "WINTERFELL_PROOF_AAAAE...",
"public_inputs": { "threshold": 2500 },
"duration_ms": 1842,
"created_at": "2026-03-16T14:23:09Z"
}
}HMAC Signature Verification
Every webhook request includes a Minerva-Signature header. Verify it to ensure the request came from Minerva and was not tampered with.
# Header format
Minerva-Signature: t=1742133660,v1=a7f3c2e9d1b84f6a2e0c5d8b3f7a1e4c9d2b5f8a3e6c1...
# To verify:
# 1. Extract timestamp (t) and signature (v1)
# 2. Construct the signed payload: "<timestamp>.<raw_body>"
# 3. Compute HMAC-SHA256 of signed payload using your webhook secret
# 4. Compare with the v1 value (constant-time comparison)import crypto from 'crypto';
export function verifyMinervaSignature(
rawBody: string,
signatureHeader: string,
webhookSecret: string
): boolean {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('='))
);
const timestamp = parts['t'];
const expected = parts['v1'];
if (!timestamp || !expected) return false;
// Reject events older than 5 minutes
if (Date.now() / 1000 - Number(timestamp) > 300) return false;
const payload = `${timestamp}.${rawBody}`;
const computed = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(expected)
);
}Retry Behaviour
- •Minerva retries failed deliveries (non-2xx or timeout) up to 5 times
- •Retry schedule: 30s, 5m, 30m, 2h, 8h after the initial failure
- •After 5 failures, the webhook is automatically deactivated
- •Respond with 2xx as quickly as possible — process events asynchronously
- •Idempotency: use the event id (evt_...) to deduplicate retries
Update a Webhook
PATCH /api/v1/webhooks/wh_01HX5MQZR9BNT5E2FPMCDVWK4 HTTP/1.1
Host: api.zkesg.com
Authorization: Bearer <your-api-key>
Content-Type: application/json
{
"events": ["proof.completed", "proof.failed", "proof.verified"],
"active": true
}Delete a Webhook
curl -X DELETE "https://api.zkesg.com/api/v1/webhooks/wh_01HX5MQZR9BNT5E2FPMCDVWK4" \
-H "Authorization: Bearer $MINERVA_API_KEY"