Mandraki Mandraki
Get started

Webhooks

Subscribe to real-time events from Mandraki -- event types, payload format, signature verification, retries, and filters.

Overview

Webhooks deliver real-time event notifications to your HTTPS endpoint when things happen in your Mandraki organization. Instead of polling the API, register a webhook to receive push notifications for message, task, call, and other events.

Each webhook subscription belongs to a service account and can subscribe to any combination of the 35 available event types with optional resource filters.

Creating a Webhook

Create a webhook subscription through the Mandraki settings UI or the service account management API.

Required Fields

FieldTypeDescription
urlURLYour HTTPS endpoint (must use https://)
eventsstring[]At least one event type to subscribe to

Optional Filters

FieldTypeDescription
channelFilterUUID[]Only deliver events for these channels
teamFilterUUID[]Only deliver events for these teams
projectFilterUUID[]Only deliver events for these projects
folderFilterUUID[]Only deliver events for these Drive folders

Empty filters (the default) match all resources of that type. Filters are additive — a webhook must pass all non-empty filters to be delivered.

Example

{
  "url": "https://your-app.example.com/webhooks/mandraki",
  "events": ["message.created", "task.created", "task.completed"],
  "channelFilter": ["channel-uuid-1", "channel-uuid-2"],
  "projectFilter": ["project-uuid-1"]
}

This webhook receives message.created events only from the two specified channels, and task events only from the specified project.

Signing Secret

When a webhook is created, Mandraki generates a signing secret and returns it once. The secret is encrypted at rest and never stored in plaintext. Store it securely — you will need it to verify webhook signatures.

You can rotate the signing secret at any time through the API. After rotation, the old secret stops working immediately.

Payload Format

Every webhook delivery sends a POST request with a JSON body:

{
  "type": "message.created",
  "event_id": "evt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "timestamp": "2026-03-27T12:00:00.000Z",
  "data": {
    "message": {
      "id": "message-uuid",
      "channel_id": "channel-uuid",
      "sender": {
        "id": "user-uuid",
        "name": "Alice"
      },
      "content": "Hello, world!",
      "is_encrypted": false,
      "created_at": "2026-03-27T12:00:00.000Z"
    }
  }
}

Envelope Fields

FieldTypeDescription
typestringThe event type (e.g., message.created)
event_idstringUnique delivery ID prefixed with evt_
timestampISO 8601When the event occurred
dataobjectEvent-specific payload

HTTP Headers

HeaderDescription
Content-Typeapplication/json
X-Mandraki-SignatureHMAC-SHA256 signature of the request body
X-Mandraki-EventThe event type
X-Mandraki-Delivery-IDUnique ID for this delivery attempt
User-AgentMandraki-Webhooks/1.0

Signature Verification

Every webhook delivery is signed with HMAC-SHA256 using your signing secret. Always verify the signature before processing the payload.

The signature is in the X-Mandraki-Signature header, formatted as sha256={hex}.

Verification Example (Node.js)

import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhookSignature(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  if (signature.length !== expected.length) return false;

  return timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/webhooks/mandraki', (req, res) => {
  const signature = req.headers['x-mandraki-signature'];
  const rawBody = req.rawBody; // raw request body string

  if (!verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(rawBody);
  // Process the event...
  res.status(200).send('OK');
});

Verification Example (Python)

import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Retry Policy

If your endpoint returns a non-2xx status code or is unreachable, Mandraki retries the delivery:

AttemptDelay
1st retry1 second
2nd retry5 seconds
3rd retry30 seconds

Each delivery attempt has a 10-second timeout. If your endpoint does not respond within 10 seconds, the attempt is considered failed.

Client errors (4xx) are not retried — they indicate a permanent problem that retrying won’t fix.

Auto-Disable

After 10 consecutive delivery failures, the webhook is automatically disabled. The disabledAt timestamp is recorded for troubleshooting.

To re-enable a disabled webhook, update it with isActive: true. This resets the failure counter.

Event Types Reference

Message Events

EventTriggerKey Payload Fields
message.createdNew message sent to a channelmessage.id, message.channel_id, message.sender, message.content, message.is_encrypted
message.updatedMessage content editedmessage.id, message.channel_id, message.content, message.is_encrypted
message.deletedMessage deletedmessage.id, message.channel_id

For E2EE-encrypted messages, the content field is null and is_encrypted is true.

Channel Events

EventTriggerKey Payload Fields
channel.createdNew channel createdchannel.id, channel.name, channel.slug, channel.is_private
channel.archivedChannel archivedchannel.id, channel.name, channel.slug
channel.member_joinedUser joined a channelchannel_id, user.id, user.name
channel.member_leftUser left a channelchannel_id, user.id, user.name

Call Events

EventTriggerKey Payload Fields
call.startedCall session beginscall.id, call.title, call.status
call.endedCall session endscall.id, call.title, call.status, call.duration
call.participant_joinedUser joins a callcall_id, participant.id, participant.name
call.participant_leftUser leaves a callcall_id, participant.id, participant.name

User Events

EventTriggerKey Payload Fields
user.joined_orgUser added to organizationuser.id, user.name, user.email
user.left_orgUser removed from organizationuser.id, user.name
user.role_changedUser’s org role changeduser.id, user.name, role

Task Events

EventTriggerKey Payload Fields
task.createdNew task createdtask.id, task.title, task.project_id, task.assignee_id, task.created_by
task.updatedTask fields modifiedtask.id, task.title, task.project_id, task.status
task.completedTask marked as donetask.id, task.title, task.project_id, task.completed_by
task.assignedTask assigned to usertask.id, task.title, task.project_id, assignee.id, assignee.name
task.commentedComment added to tasktask.id, task.title, task.project_id, comment.id, comment.author
task.deletedTask deletedtask.id
project.createdNew project createdproject.id, project.name, project.team_id
project.updatedProject modifiedproject.id, project.name, project.team_id

Drive Events

EventTriggerKey Payload Fields
drive.file.uploadedFile uploadedfile.id, file.name, file.folder_id, file.size, file.mime_type, file.uploaded_by
drive.file.deletedFile deletedfile.id
drive.file.sharedFile shared with userfile.id, file.name, file.folder_id, shared_with.id, shared_with.name
drive.folder.createdFolder createdfolder.id, folder.name, folder.parent_id, folder.created_by
drive.folder.deletedFolder deletedfolder.id

Calendar Events

EventTriggerKey Payload Fields
calendar.event.createdEvent createdevent.id, event.title, event.start_time, event.end_time, event.created_by
calendar.event.updatedEvent modifiedevent.id, event.title, event.start_time, event.end_time
calendar.event.cancelledEvent cancelledevent.id
calendar.event.rsvpUser RSVPs to eventevent.id, event.title, user.id, user.name, response

Mail Events

Mail webhooks deliver metadata only — message body content is never included.

EventTriggerKey Payload Fields
mail.receivedInbound email receivedmail.id, mail.subject, mail.from, mail.to, mail.received_at
mail.sentOutbound email sentmail.id, mail.subject, mail.from, mail.to, mail.sent_at

Team Events

EventTriggerKey Payload Fields
team.createdTeam createdteam.id, team.name, team.slug, team.created_by
team.archivedTeam archivedteam.id, team.name
team.deletedTeam deletedteam.id, team.name
team.member_joinedUser joined a teamteam_id, user.id, user.name
team.member_leftUser left a teamteam_id, user.id, user.name

Test Webhooks

You can send a test event to verify your endpoint is configured correctly. The test payload uses the event type test:

{
  "type": "test",
  "event_id": "evt_...",
  "timestamp": "2026-03-27T12:00:00.000Z",
  "data": {
    "message": "This is a test webhook from Mandraki"
  }
}

Best Practices

  • Respond quickly: Return a 200 status within 10 seconds. Process events asynchronously if needed.
  • Use idempotent processing: Use the event_id to deduplicate — retries may deliver the same event more than once.
  • Verify signatures: Always validate the X-Mandraki-Signature header using constant-time comparison.
  • Use filters: Subscribe only to the events and resources you need to reduce noise and processing load.
  • Monitor failures: Check your webhook status regularly. After 10 consecutive failures, the webhook is auto-disabled.
  • Handle E2EE: Encrypted message content is null. Check is_encrypted before processing message payloads.