Skip to Content
v1.36.0 · shipped One-screen report · Looks like your app Read the changelog →
SDK reference@mushi-mushi/plugin-sdk

@mushi-mushi/plugin-sdk

Write your own small HTTPS server that reacts when Mushi Mushi files, triages or fixes a bug. The SDK checks Mushi Mushi’s signature on each request, drops repeat deliveries and calls your handler for the event.

You only need it to connect a tool Mushi Mushi does not cover. Slack, Discord, Teams, Linear, Jira, GitHub Issues, PagerDuty and Sentry are connected on the Integrations page of the console with no code.

Install

npm i @mushi-mushi/plugin-sdk express

Express adapter

createPluginHandler takes your secret and one handler per event ('*' catches the rest). expressMiddleware wraps it and reads the raw body itself, so mount it before express.json().

import express from 'express' import { createPluginHandler, createMushiClient } from '@mushi-mushi/plugin-sdk' import { expressMiddleware } from '@mushi-mushi/plugin-sdk/express' const mushi = createMushiClient({ apiKey: process.env.MUSHI_API_KEY!, projectId: process.env.MUSHI_PROJECT_ID!, }) const handler = createPluginHandler({ secret: process.env.MUSHI_PLUGIN_SECRET!, on: { 'report.classified': async (envelope) => { const { report, classification } = envelope.data as { report: { id: string } classification: { severity: string } } if (classification.severity === 'critical') { await mushi.comment(report.id, 'Triaged as critical, paging on-call.') } }, }, }) const app = express() app.post('/mushi/webhook', expressMiddleware(handler)) app.listen(8080)

Hono adapter

import { Hono } from 'hono' import { createPluginHandler } from '@mushi-mushi/plugin-sdk' import { honoHandler } from '@mushi-mushi/plugin-sdk/hono' const handler = createPluginHandler({ secret: Deno.env.get('MUSHI_PLUGIN_SECRET')!, on: { '*': async (envelope) => console.log(envelope.event, envelope.data) }, }) const app = new Hono() app.post('/mushi/webhook', honoHandler(handler))

Verify a signature manually

Using another framework? Pass the exact raw body and the X-Mushi-Signature header to verifySignature:

import { verifySignature } from '@mushi-mushi/plugin-sdk' const result = verifySignature({ rawBody, header: req.headers['x-mushi-signature'] as string, secret: process.env.MUSHI_PLUGIN_SECRET!, }) if (!result.ok) throw new Error(result.reason)

For the Standard Webhooks headers, use verifyStandardWebhooksSignature({ rawBody, webhookId, webhookTimestamp, webhookSignature, secret }). It returns the same { ok, reason } shape.

Wire format

Mushi Mushi POSTs JSON envelopes shaped like:

{ "event": "report.classified", "deliveryId": "5b0e…", "occurredAt": "2026-04-17T18:42:00.000Z", "projectId": "8c1d…", "pluginSlug": "your-plugin", "data": { "report": { "id": "rep_01HXY…", "status": "classified" }, "classification": { "category": "bug", "severity": "high", "confidence": 0.92, "tags": ["payments.stripe-3ds"] } } }

Each request carries two signatures over the same body:

X-Mushi-Signature: t=<unix-ms>,v1=<hex hmac_sha256(secret, "<t>.<rawBody>")> webhook-id: <deliveryId> webhook-timestamp: <unix-secs> webhook-signature: v1,<base64 hmac_sha256(secret, "<webhook-id>.<webhook-timestamp>.<rawBody>")>

createPluginHandler checks whichever is present. The default tolerance is 5 minutes; pass toleranceMs to verifySignature to change it. deliveryId is unique per delivery, so use it to ignore repeats (the handler does this in memory by default; pass dedupStore to persist it).

See also

Last updated on