@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 expressExpress 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
- Plugin marketplace overview
- Build your own plugin
- Reference plugins: PagerDuty, Linear, Zapier