Agent skill

Meilisearch Webhooks Skill

Receive and authenticate Meilisearch task webhooks. Use when setting up a Meilisearch webhook endpoint (Cloud project webhooks, self-hosted --task-webhook-url, or the /webhooks API), checking the Authorization header, decoding the gzipped ndjson body, debugging 401s, or handling finished tasks such as documentAdditionOrUpdate, settingsUpdate and indexDeletion.

Install this skill

npx skills add hookdeck/webhook-skills --skill meilisearch-webhooks


Meilisearch (open-source search engine) sends an HTTP POST to your endpoint whenever tasks finish. Payloads are not signed: the only credential is the Authorization header you configure. The body is gzip-compressed ndjson with one finished task object per line.

When to Use This Skill

  • How do I receive Meilisearch webhooks?
  • How do I verify the Meilisearch webhook Authorization header?
  • Does Meilisearch sign webhook payloads? (No: shared Authorization value only)
  • Why is my Meilisearch webhook body unreadable / why does JSON.parse fail on it?
  • How do I handle documentAdditionOrUpdate or failed tasks from a webhook?
  • Why does my Meilisearch webhook endpoint return 401?

Verification (core)

Set the Authorization header when you create the webhook (Meilisearch Cloud: Project Settings > Webhooks; self-hosted: --task-webhook-authorization-header). Meilisearch sends it verbatim, so store the whole value exactly as set in Meilisearch, including any Bearer or Basic prefix, and compare the whole header to it. Don't strip, parse or base64-decode it.

const crypto = require('crypto');
const zlib = require('zlib');

function safeEqual(a, b) { // hash both sides: equal-length buffers, never throws
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

const expected = process.env.MEILISEARCH_AUTHORIZATION; // e.g. "Bearer your-secret-token"
const received = req.headers['authorization'];
if (!expected || !received || !safeEqual(received, expected)) return res.status(401).end();

let body = req.body; // raw Buffer
if (/gzip/i.test(req.headers['content-encoding'] || '')) body = zlib.gunzipSync(body); // 400 on error
const tasks = body.toString('utf8').split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));

Check the credential first, then gunzip, then JSON.parse each line, never the whole body. Return 401 for a missing or wrong header, 400 for a body that can't be gunzipped or parsed, and 2xx once handled. Meilisearch Cloud documents retries with exponential backoff for non-2xx responses; the self-hosted docs don't mention retries, and the open-source engine sends each request once with a 30-second timeout, so a failure there is lost. Python and Next.js equivalents are in references/verification.md.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Task Types and Statuses

Each ndjson line is a finished task object (same shape as the Tasks API: uid, indexUid, status, type, details, error, ...). Route on type and status:

typeMeaning
documentAdditionOrUpdateDocuments added or updated (details.indexedDocuments)
documentDeletionDocuments deleted
settingsUpdateIndex settings changed
indexCreation / indexUpdate / indexDeletionIndex lifecycle
indexSwapIndexes swapped
dumpCreation / snapshotCreationBackups
taskCancelation / taskDeletionTask queue maintenance

status is succeeded, failed or canceled (only finished tasks are sent).

Environment Variables

MEILISEARCH_AUTHORIZATION=Bearer your-secret-token   # full header value, prefix included

Local Development

npx hookdeck-cli listen 3000 meilisearch --path /webhooks/meilisearch

No account required. Use the printed URL as the webhook URL. Hookdeck stores gzip bodies as binary, so events show a binary body in the dashboard; it forwards the original bytes and Content-Encoding header. In Hookdeck's Meilisearch source (type MEILISEARCH): paste the Authorization header value exactly as set in Meilisearch, including any "Bearer " or "Basic " prefix.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →