Opengram

Webhooks

Receive real-time HTTP callbacks when events occur in your Opengram instance.

Webhooks let your backend receive HTTP POST notifications when specific events occur in Opengram. This is useful for logging, triggering external workflows, or integrating with systems that cannot poll the API.

Configuration

Define webhooks in the hooks array of opengram.config.json. Each hook object supports:

FieldRequiredDefaultDescription
urlYes--The endpoint Opengram will POST to.
eventsYes--Array of event names to subscribe to.
headersNo{}Extra headers to include in the request.
signingSecretNo--Secret used to sign payloads for verification.
timeoutMsNo5000Request timeout in milliseconds.
maxRetriesNo3Maximum number of retry attempts on failure.

Example

opengram.config.json
{
  "hooks": [
    {
      "url": "https://api.example.com/opengram/webhook",
      "events": ["message.created", "request.resolved"],
      "signingSecret": "whsec_your_secret_here",
      "headers": {
        "X-Custom-Header": "my-value"
      },
      "timeoutMs": 10000,
      "maxRetries": 5
    }
  ]
}

Supported events

Messages

EventDescription
message.createdA new message was created in a chat (by a user or an agent).
message.streaming.completeAn agent message finished streaming (or was cancelled mid-stream).

Requests

EventDescription
request.createdA new interactive request was created.
request.resolvedAn interactive request was resolved by the user.
request.cancelledAn interactive request was cancelled.

Chats

EventDescription
chat.createdA new chat was created.
chat.updatedA chat's title, tags, or other metadata changed.
chat.archivedA chat was archived.
chat.unarchivedA chat was unarchived.
chat.readA chat was marked as read.
chat.unreadA chat was marked as unread.

Media

EventDescription
media.attachedA media file was attached to a chat.

Payload

Each webhook delivery is an HTTP POST with a JSON body. Every delivery is wrapped in an event envelope:

{
  "id": "evt_abc123",
  "type": "message.created",
  "timestamp": "2025-03-01T19:00:00.000Z",
  "payload": { ... }
}

The payload fields vary by event type. For message.created and request.* events, Opengram enriches the payload with additional data so your handler does not need to make extra API calls. Other events pass through the raw payload as emitted.

message.created

{
  "chatId": "chat_456",
  "agentIds": ["assistant"],
  "messageId": "msg_789",
  "senderId": "user",
  "role": "user",
  "content": "Hello, can you help me?",
  "modelId": null,
  "pendingRequestsCount": 0,
  "trace": null
}

request.resolved / request.cancelled

{
  "chatId": "chat_456",
  "requestId": "req_123",
  "type": "confirmation",
  "status": "resolved",
  "resolutionPayload": { "confirmed": true },
  "trace": null
}

message.streaming.complete

{
  "chatId": "chat_456",
  "messageId": "msg_789",
  "role": "assistant",
  "streamState": "complete",
  "finalText": "Here is my response."
}

request.created

{
  "chatId": "chat_456",
  "requestId": "req_123",
  "type": "confirmation",
  "title": "Deploy to production?"
}

media.attached

{
  "chatId": "chat_456",
  "mediaId": "media_789",
  "messageId": "msg_123",
  "kind": "image"
}

chat.* events

Chat lifecycle events (chat.created, chat.updated, chat.archived, chat.unarchived, chat.read, chat.unread) contain:

{
  "chatId": "chat_456"
}

Signature verification

When a signingSecret is configured, Opengram includes an X-OpenGram-Signature header with every delivery. The signature is computed as:

sha256=<hex-encoded HMAC-SHA256 of the raw request body>

To verify a webhook in your handler:

  1. Read the raw request body as bytes.
  2. Compute the HMAC-SHA256 of the body using your signingSecret as the key.
  3. Compare the hex-encoded result with the value after sha256= in the header.
  4. Use a constant-time comparison to prevent timing attacks.

Node.js example

import { createHmac, timingSafeEqual } from "crypto";

function verifySignature(body, secret, signatureHeader) {
  const hmac = createHmac("sha256", secret);
  hmac.update(body);
  const expected = `sha256=${hmac.digest("hex")}`;
  return (
    expected.length === signatureHeader.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
  );
}

Retry logic

When a delivery fails, Opengram retries with exponential backoff up to the configured maxRetries:

  • 4xx responses are treated as permanent failures and are not retried.
  • 5xx responses and network errors are retried.
  • The backoff delay increases exponentially between attempts.

After all retry attempts are exhausted, the delivery is marked as failed.

Delivery logging

Every webhook delivery attempt is logged in the webhook_deliveries table with the HTTP status code, success/failure status, and any error message. Delivery records are retained for 30 days alongside their parent events. This log is useful for debugging delivery issues.

On this page