Skip to main content
Zyra Zyra
Features Pricing Security FAQ Documentation
Sign In Sign up for free

Documentation › User Guides › Organizations › Stage 4 › Receive webhooks from Zyra

Receive webhooks from Zyra

Chapter 2 · about 15 minutes to read

Polling the API works, but it wastes calls and adds latency. Webhooks invert the flow: Zyra POSTs to a URL you own whenever something interesting happens. This chapter covers subscription, signature verification, retries, and the dashboard's delivery history.

Time: about 15 minutes. Prerequisites: an HTTPS endpoint you control (ngrok or a real server). An Org Admin account.

Step 1 — Register your endpoint

Open Settings → Webhooks → New endpoint, or POST to the API:

curl -X POST https://app.getzyra.io/api/v1/webhooks/endpoints \
  -H "Authorization: Bearer $ZYRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.acme.example/zyra",
    "events": ["virtual_server.started", "virtual_server.stopped", "job.completed"],
    "description": "prod ops bus"
  }'

Zyra stores the URL, generates a 32-byte signing secret, and returns it once. Limits: 10 endpoints per organization.

[SCREENSHOT: Webhook settings page with the New endpoint form open]

The event catalogue

Events you can subscribe to today, in dot-notation:

  • device.online, device.offline, device.approved, device.removed
  • virtual_server.created, .started, .stopped, .terminated, .error
  • job.queued, .started, .completed, .failed
  • sla.breach.started, sla.breach.resolved
  • invoice.generated, invoice.paid, payment.failed

A subscription only receives events on its events list. [VERIFY: full event enum path — today event_type is a free-form string per WebhookDelivery model]

Step 2 — Verify the signature

Every POST carries two headers: X-Zyra-Event (the event type) and X-Zyra-Signature (HMAC-SHA256 hex digest of the JSON body, signed with your endpoint secret). Always verify — an unsigned POST to your URL could come from anywhere.

Python

import hmac, hashlib
sent = req.headers["X-Zyra-Signature"]
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sent, expected):
    raise HTTPException(401, "bad signature")

Node.js

const expected = crypto.createHmac("sha256", SECRET)
                       .update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
  return res.status(401).end();
}

Step 3 — Acknowledge fast

Return HTTP 2xx within 10 seconds. Anything else counts as a failure. If you need to do heavy work, push the event onto your own queue and return 200 immediately.

Retry policy

Failed deliveries retry with exponential backoff: 5 attempts, base 2 seconds. After 5 attempts the row is marked expired and dropped. Successive failures bump WebhookEndpoint.failure_count and stamp last_failure_at so you can spot a misbehaving consumer from the dashboard.

A nightly webhook_cleanup background job removes delivery rows older than 30 days.

Step 4 — Inspect delivery history

The dashboard ships a per-endpoint Deliveries tab backed by GET /api/v1/webhooks/endpoints/{id}/deliveries. Each row shows event type, attempt count, HTTP response status, the first 2 KB of the response body, and the final status (pending / delivered / failed / expired).

[SCREENSHOT: Deliveries tab with one delivered row expanded showing payload and 200 response]

Step 5 — Test before going live

POST a synthetic event to confirm the receiver works:

curl -X POST https://app.getzyra.io/api/v1/webhooks/endpoints/$ID/test \
  -H "Authorization: Bearer $ZYRA_API_KEY"

This dispatches a webhook.test event with a sample payload. If your URL responds 200, you're done.

What just happened

You registered an endpoint, verified signatures, learned the retry policy, and tested it end-to-end. Cross-link to Chapter 4: audit log if you also want a persistent record of every event.

Troubleshooting

  • Endpoint shows failure_count > 0 but you got nothing. Check the Deliveries tab. Common cause: your signature verification rejected the request.
  • Events you expect aren't firing. Confirm the event name is on your endpoint's events array.
  • Localhost / 127.0.0.1 URLs are rejected. SSRF protection blocks loopback addresses. Use ngrok or a public URL in development.

← Previous: Use the Zyra API

Next: SSO and SAML →

Last reviewed: 2026-05-21

© 2026 Zyra. All rights reserved. | Privacy Policy | Terms of Service | Careers