1. Home
  2. Help Center
  3. Webhooks & integrations
  4. Get notified with webhooks

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

  1. Open your project → Developer → Webhooks → New webhook.
  2. Give it a name, paste your https endpoint URL, and check the events you care about.
  3. Save. We show the signing secret once — copy it now; it's used to verify each delivery.
  4. 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 2xx response 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 id field 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.