Get notified with webhooks
Webhooks: real-time events from your project
Webhooks let your backend react to things happening in AppGram — a customer opening a ticket, your team replying, a release going live — without polling. We send a signed HTTP POST to a URL you control whenever a subscribed event fires.
What you can subscribe to
- Wishes — created, updated, status changed, comment added, vote added.
- Support — ticket created, reply (admin or customer), ticket closed.
- Status incidents — opened, updated, resolved.
- Monitors — alert triggered, alert resolved.
- Contact forms — new submission.
- Billing (org-scoped) — subscription created/updated/canceled, payment succeeded/failed, trial ending.
Setting up a webhook
- Open your project → Developer → Webhooks → New webhook.
- Give it a name, paste your https endpoint URL, and check the events you care about.
- Save. We show the signing secret once — copy it now; it's used to verify each delivery.
- Click "Send test event" to make sure your receiver works before any real traffic flows.
Billing-related webhooks live at the organization level: Organization → Developer → Webhooks.
What the payload looks like
POST https://your-server.example.com/webhooks/appgram
Content-Type: application/json
X-Webhook-ID: <unique per attempt>
X-Webhook-Timestamp: <unix seconds>
X-Webhook-Signature: t=<ts>,v1=<hex hmac>
{
"id": "evt_...",
"event": "support_ticket_reply",
"timestamp": "...",
"data": {
"entity_id": "...",
"entity_type": "support_ticket",
"title": "...",
"description": "...",
"metadata": { }
},
"metadata": {
"organization_id": "...",
"project_id": "..."
}
}
Verifying the signature
Compute HMAC-SHA256 over <timestamp>.<raw body> using your secret. Compare to the v1=... value:
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const ok = crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(receivedSignature, 'hex'),
);
Always verify with the raw request body, not a re-serialized JSON object — any whitespace change breaks the signature.
Reliability
- We expect a
2xxresponse within 10 seconds. Anything else is a failure. - Failed deliveries are automatically retried with exponential backoff (30s, 2m, 10m, 30m, 2h, 6h, 24h — 8 attempts over ~33 hours).
- The
idfield is stable across retries. Store it and short-circuit duplicates — your endpoint should be idempotent. - Delivery logs (status, response code, attempts) are visible per webhook in the dashboard for the last 30 days.
Heads-up
- Endpoints must be reachable over
https://; we don't deliver to plain HTTP. - You can rotate the signing secret at any time from the webhook's detail page — old deliveries stay signed with whatever secret was active at send time.
- Disabling a webhook stops new deliveries but doesn't cancel queued retries; delete to fully purge.