Webhooks
Register HTTPS endpoints to receive real-time notifications when events occur.
4 min read · Last updated:
Overview
Webhooks notify your server in real-time when events happen in Ergate — a proposal finishes generating, an export completes, an outcome is recorded, etc.
When an event occurs, Ergate sends an HTTP POST to your registered endpoint with a signed JSON payload. Your server should respond with a 2xx status code.
Event types
proposal.created — New proposal created
proposal.updated — Proposal fields updated
proposal.analysis_completed — AI analysis finished successfully
proposal.analysis_failed — AI analysis errored
proposal.generation_completed — Proposal document generated
proposal.generation_failed — Generation errored
proposal.scoring_completed — Proposal scored
proposal.scoring_failed — Scoring errored
proposal.exported — Proposal exported to PDF/DOCX/PPTX
proposal.outcome_recorded — Outcome set (won/lost/no_response/cancelled)
test.ping — Test event sent from dashboard or API
Source filtering
Each endpoint can filter events by where they originated:
ui — Events triggered from the Ergate dashboard
api — Events triggered via the API
By default, endpoints receive events from both sources.
Payload format
Every webhook delivery is an HTTP POST with these headers:
Content-Type: application/json
X-Ergate-Signature: sha256=<HMAC-SHA256 hex digest>
X-Ergate-Event: proposal.analysis_completed
X-Ergate-Delivery: evt_550e8400-e29b-41d4-a716-446655440000
X-Ergate-Timestamp: 1679054460
User-Agent: Ergate-Webhooks/1.0
Body
{
"id": "evt_550e8400-e29b-41d4-a716-446655440000",
"type": "proposal.analysis_completed",
"createdAt": "2026-03-17T10:30:00.000Z",
"data": {
"proposalId": "360db97a-516e-43f7-a14f-91e4cf829551",
"status": "analyzed",
"title": "Website Redesign Proposal",
"source": "api"
}
}
Verifying signatures
Always verify the X-Ergate-Signature header before processing a webhook. This confirms the event was sent by Ergate and hasn't been tampered with.
The signature is an HMAC-SHA256 digest of the raw request body, using your endpoint's secret as the key, prefixed with sha256=.
Node.js:
import { createHmac, timingSafeEqual } from "crypto";
function verifySignature(body: Buffer, secret: string, signature: string): boolean {
const expected = "sha256=" +
createHmac("sha256", secret).update(body).digest("hex");
if (expected.length !== signature.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Python:
import hashlib
import hmac
def verify_signature(payload: bytes, secret: str, signature: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Retry policy
- Failed deliveries are retried up to 3 times with exponential backoff: 1 minute, 5 minutes, 15 minutes.
- After 10 consecutive failures across events, the endpoint is automatically disabled.
- Re-enable a disabled endpoint by setting
isActive: truevia the API or dashboard. - Events expire after 30 days.
Endpoint management
List endpoints
GET /api/v1/webhooks
Scope: webhooks:manage
Register endpoint
POST /api/v1/webhooks
Scope: webhooks:manage
Request body
{
"url": "https://example.com/webhooks/ergate",
"events": ["proposal.created", "proposal.analysis_completed"],
"sources": ["api"]
}
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| url | string | Yes | HTTPS URL to receive events |
| events | string[] | Yes | Event types to subscribe to (min 1) |
| sources | string[] | No | ["ui"], ["api"], or ["ui", "api"] (default: both) |
Response — 201 Created
{
"data": {
"id": "d09dcbf8-...",
"url": "https://example.com/webhooks/ergate",
"events": ["proposal.created", "proposal.analysis_completed"],
"sources": ["api"],
"secret": "whsec_abc123...",
"isActive": true,
"failureCount": 0,
"createdAt": "2026-03-17T10:30:00.000Z"
},
"meta": { "requestId": "...", "timestamp": "..." }
}
The secret is only returned at creation time. Store it securely — you'll need it to verify webhook signatures.
Update endpoint
PATCH /api/v1/webhooks/:id
Scope: webhooks:manage
All fields are optional. Setting isActive: true resets the failure counter.
{
"url": "https://example.com/webhooks/ergate-v2",
"events": ["proposal.created", "proposal.analysis_completed", "proposal.exported"],
"sources": ["ui", "api"],
"isActive": true
}
Delete endpoint
DELETE /api/v1/webhooks/:id
Scope: webhooks:manage
Test endpoint
POST /api/v1/webhooks/:id/test
Scope: webhooks:manage
Sends a test.ping event and reports whether delivery succeeded.
{
"data": {
"success": true,
"httpStatus": 200,
"error": null
},
"meta": { "requestId": "...", "timestamp": "..." }
}
Query event log
GET /api/v1/webhooks/events
Scope: webhooks:manage
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| endpointId | uuid | — | Filter by endpoint |
| eventType | string | — | Filter by event type |
| status | string | — | pending, delivered, failed, expired |
| page | integer | 1 | Page number |
| limit | integer | 20 | Items per page (max 50) |