agentsclimarketplace

Masheev webhooks

Skill masheev/skills/skills/masheev-webhooks

Official Agent Skills for integrating Masheev into your applications

Install
npx -y skills add masheev/skills --skill masheev-webhooks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when setting up, verifying, or handling Masheev webhook events in your backend. Covers creating webhook subscriptions, HMAC-SHA256 signature verification with timingSafeEqual, raw body preservation before JSON parsing, idempotent event handling, retry logic, and all 11 event types (conversation.created, message.received, contact.updated, etc.). Use this skill whenever someone asks to "handle Masheev events", "set up webhooks", "verify webhook signatures", or wants real-time notifications from Masheev. Also use when debugging webhook delivery failures or missed events.

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

6.9 KB, as published. Nobody here has run it

Masheev Webhooks

Receive real-time HTTP callbacks when events happen in Masheev. Every webhook is signed with HMAC-SHA256 so you can verify it came from Masheev.

Quick Start

1. Create a Webhook (Dashboard or API)

Via the Masheev dashboard: Settings > Developers > Webhooks > Create webhook

Or via API:

import { apiClient } from "@masheev/client/server";

await apiClient.webhooks.create.mutate({
  orgId: "org_...",
  name: "My App Events",
  url: "https://myapp.com/api/webhooks/masheev",
  events: ["conversation.created", "message.received", "contact.updated"],
});

2. Handle and Verify the Webhook

// Next.js App Router — app/api/webhooks/masheev/route.ts
import crypto from "node:crypto";

const WEBHOOK_SECRET = process.env.MASHEEV_WEBHOOK_SECRET!;

export async function POST(request: Request) {
  // Step 1: Get raw body BEFORE parsing JSON
  const rawBody = await request.text();
  const signature = request.headers.get("x-webhook-signature");

  if (!signature) {
    return new Response("Missing signature", { status: 401 });
  }

  // Step 2: Verify HMAC-SHA256 signature
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );

  if (!isValid) {
    return new Response("Invalid signature", { status: 401 });
  }

  // Step 3: Parse and handle the event
  const event = JSON.parse(rawBody);

  switch (event.type) {
    case "message.received":
      await handleNewMessage(event.payload);
      break;
    case "conversation.created":
      await handleNewConversation(event.payload);
      break;
    case "contact.updated":
      await handleContactUpdate(event.payload);
      break;
  }

  // Step 4: Return 200 quickly (Masheev retries on non-2xx)
  return new Response("OK", { status: 200 });
}

Signature Verification

Every webhook includes an x-webhook-signature header containing the HMAC-SHA256 hex digest of the raw request body.

Correct (timing-safe comparison):

const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const isValid = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

Wrong (vulnerable to timing attacks):

// NEVER do this
if (signature === expected) { ... }

Wrong (body already parsed — signature won't match):

// NEVER do this — JSON.stringify reorders keys, changes whitespace
const body = await request.json();
const rawBody = JSON.stringify(body); // This is NOT the original raw body

Framework-Specific Raw Body Access

FrameworkRaw Body Access
Next.js App Routerawait request.text()
Next.js Pages RouterSet config.api.bodyParser = false, read req as stream
Expressapp.use("/webhooks", express.raw({ type: "application/json" }))
Honoawait c.req.text()
Fastifyfastify.addContentTypeParser("application/json", { parseAs: "string" }, ...)

See references/frameworks.md for complete examples in each framework.

Event Types

EventTriggerKey Payload Fields
conversation.createdNew conversation startedconversationId, contactId, inboxId, channel
conversation.updatedStatus, priority, or assignment changedconversationId, status, priority, assigneeId
conversation.closedConversation resolvedconversationId, resolvedAt, outcome
conversation.assignedAssigned to agent or AIconversationId, assigneeType, assigneeId
message.receivedMessage from contactmessageId, conversationId, role: "contact", content
message.sentMessage sent by agent or AImessageId, conversationId, role, content, status
message.failedMessage delivery failedmessageId, conversationId, status: "failed", error
contact.createdNew contact identifiedcontactId, name, email, phone
contact.updatedContact info changedcontactId, changed fields
escalation.createdConversation escalated to humanescalationId, conversationId, reason
escalation.resolvedEscalation handledescalationId, conversationId, status: "resolved"

See references/events.md for complete payload schemas.

Webhook Payload Format

Every webhook delivery has this shape:

{
  id: string;          // Unique event ID (for idempotency)
  type: string;        // e.g., "conversation.created"
  timestamp: number;   // Unix ms
  payload: { ... };    // Event-specific data
}

Idempotent Handling

Masheev may deliver the same event more than once (retries on timeout/error). Use the id field to deduplicate:

// Simple in-memory dedup (use Redis/DB for production)
const processed = new Set<string>();

async function handleWebhook(event: WebhookEvent) {
  if (processed.has(event.id)) return; // Already handled
  processed.add(event.id);

  // Process the event...
}

Retry Behavior

  • Masheev retries on non-2xx responses and timeouts
  • Retry schedule: 1 min, 5 min, 30 min, 2 hours, 24 hours
  • Webhooks are paused after 7 consecutive days of failures
  • Respond with 200 within 30 seconds to avoid timeout retries

Testing Webhooks

# Test delivery from the Masheev dashboard
# Settings > Developers > Webhooks > [your webhook] > Send Test

# Or via API
await apiClient.webhooks.test.mutate({
  id: "wh_...",
  orgId: "org_...",
});

For local development, use a tunnel:

# ngrok
ngrok http 3000

# Then update your webhook URL to the ngrok URL

Managing Webhooks

// List all webhooks
const webhooks = await apiClient.webhooks.list.query({ orgId: "org_..." });

// Update events
await apiClient.webhooks.update.mutate({
  id: "wh_...",
  orgId: "org_...",
  events: ["message.received", "message.sent"],
});

// Rotate the signing secret
await apiClient.webhooks.regenerateSecret.mutate({
  id: "wh_...",
  orgId: "org_...",
});

// Delete
await apiClient.webhooks.delete.mutate({ id: "wh_...", orgId: "org_..." });

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.