Webhooks
Receive, verify, and reliably process workspace events
Webhooks notify your application when something changes in Verifycate. Instead of repeatedly polling the API, your server receives an HTTPS request shortly after a certificate, recipient, request, correction, or engagement event.
Use webhooks to update an internal learner record, trigger a downstream workflow, build an audit trail, or measure recipient engagement. Webhooks require an active Pro seat.
How it works
- Configure one or more HTTPS endpoints in Settings → Webhooks or with Create a webhook.
- Choose individual event types, or subscribe to
*to receive every event. - Verify the signature of every request before parsing or acting on it.
- Process the event idempotently and return any HTTP
2xxstatus promptly. - Use List deliveries to investigate retries and failures.
Verifycate sends requests with these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Verifycate-Webhook/1.0 |
X-Verifycate-Signature | Hex-encoded HMAC-SHA256 signature |
Payload
Every delivery is a JSON POST:
{
"id": "7c8e0b2a-3d1f-4c55-9a11-2f6b8d4e1c90",
"event_type": "certificate.issued",
"occurred_at": "2026-09-03T12:00:00Z",
"workspace_id": "550e8400-e29b-41d4-a716-446655440000",
"certificate_id": "8ac68e9d-4aca-43ca-88f0-f2220f849626",
"issue_id": "1069340d-5b74-4670-a319-41b8fe3a0e5d",
"data": {
"recipient_email": "[email protected]"
}
}| Field | Meaning |
|---|---|
id | Stable event identifier; use it as an idempotency key |
event_type | The event that occurred |
occurred_at | When the event was recorded, as an RFC 3339 timestamp |
workspace_id | Workspace that owns the event |
certificate_id | Related certificate when the event has one |
issue_id | Related certificate issue when the event has one |
data | Event-specific data; it may be an empty object |
Do not assume deliveries arrive in order. A retry for an older event can arrive after a newer event.
Verify the signature
Each endpoint has a secret returned when it is created. Store the secret in a secret manager; do not commit it or expose it to browser code.
Calculate HMAC-SHA256 over the exact raw request bytes using the endpoint
secret, hex-encode the digest, and compare it with
X-Verifycate-Signature using a constant-time comparison. Verify the signature
before decoding JSON—the parsed and re-serialized body may not have the same
bytes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyVerifycateWebhook(rawBody, receivedSignature, secret) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const expectedBytes = Buffer.from(expected, "hex");
const receivedBytes = Buffer.from(receivedSignature ?? "", "hex");
return (
expectedBytes.length === receivedBytes.length && timingSafeEqual(expectedBytes, receivedBytes)
);
}Reject missing or invalid signatures with an HTTP 400 or 401 response.
Acknowledge deliveries
Return a 2xx response only after your application has durably accepted the
event. If processing is slow, write the verified event to your own queue and
respond immediately; the delivery request has a 10-second timeout.
Redirects and non-2xx responses are treated as failures. Verifycate stores at
most the first 2 KB of a failure response for troubleshooting.
Retries
Failed deliveries are retried with this backoff schedule:
| Retry | Delay after failure |
|---|---|
| 1 | 30 seconds |
| 2 | 2 minutes |
| 3 | 15 minutes |
| 4 | 1 hour |
| 5 | 4 hours |
| 6 | 12 hours |
| 7 | 24 hours |
After the initial attempt and seven unsuccessful retries, the delivery is
marked as permanently failed. Network errors, timeouts, and every non-2xx
status follow the same retry policy.
Because a response can be lost after your server processes an event, the same
event may be delivered more than once. Put a unique constraint on the payload
id, or record processed IDs before applying side effects.
Event types
Certificate lifecycle
recipient.enrolled, certificate.issued, certificate.bulk_sent,
certificate.reissued, certificate.amended, certificate.deleted,
certificate.created, certificate.updated, certificate.archived,
template.published
Requests and corrections
request.submitted, request.approved, request.rejected,
request.reopened, correction.submitted, correction.approved,
correction.rejected
Recipient engagement
email.opened, email.clicked, certificate.opened_from_email,
certificate.downloaded, share.linkedin_add, share.linkedin, share.x,
share.facebook, share.whatsapp, share.telegram, share.email,
share.link_copied, certificate.verified
Forms
form_invite.sent, request_form.opened, request_form.submitted
Subscribe only to the events your application handles. Use * when forwarding
events to a general-purpose queue or data warehouse.
Operational checklist
- Serve the endpoint over HTTPS and keep it reachable from the public internet.
- Verify signatures against the raw body before parsing JSON.
- Deduplicate by event
idand make handlers safe to retry. - Return
2xxquickly; move slow work to a queue. - Log the event ID and type, but avoid logging unnecessary recipient data.
- Monitor delivery history for permanent failures.
- Update subscriptions with Update a webhook when your application starts or stops handling an event.