Documentation menu

Webhooks & events

Account webhooks send what happens across your whole account to a URL you choose, as it happens: a new lead, a booked meeting, a deal moving stage. They're what the Zapier and n8n integrations use under the hood, and you can point them at your own endpoint too.

They sit alongside the project webhook on a single email's Responses page, which keeps working as before. A project webhook covers one email's interactions. An account webhook covers every email, plus the events below, and is signed the same way.

Add a webhook

  1. Go to Developers in the dashboard and find the Webhooks card.
  2. Enter the Endpoint URL (a public https:// URL) and tick the Events you want.
  3. Click Add webhook and copy the signing secret. It's shown only this once.

Each webhook shows how it was created (Dashboard, API, Zapier or n8n), its status, and when it last delivered. Send test posts a sample of the chosen event, with "test": true in its data. Delete removes the webhook. An account can have up to 50.

Only the account owner can add or delete webhooks, and you may be asked to sign in again first.

Events

EventFires when
interaction.receivedA recipient interacts with an email: a poll vote, rating, quiz answer, tracked click, purchase and so on. Repeats that are deduplicated don't fire.
form.submittedA form in an email is submitted (source: "email-form"), or a signup form is (source: "signup-form").
contact.createdA contact is added to a list.
contact.updatedA contact's fields change. Saving identical data doesn't fire.
lead.hotA contact's engagement score crosses your hot-lead threshold on the way up.
lead.newA new lead arrives from a signup form or a form in an email.
booking.createdA meeting is booked.
booking.cancelledA booking is cancelled, with cancelledBy. Rescheduling fires neither booking event.
deal.createdA deal is created, from anywhere: the board, an auto-rule, a journey, a proposal or the API.
deal.stage_changedA deal moves stage, won and lost included.
purchase.completedA recipient pays through an in-email checkout.
journey.completedA contact reaches the end of a journey. A contact who exits early doesn't count.

The contact events fire for imports, signup forms, the API, syncs and AI connectors. A single write of more than 500 contacts (a large CSV import, for instance) fires none, so an import doesn't start thousands of workflows. Edits in the contact grid, journey Update field steps and saved answers don't fire them either.

lead.hot and lead.new fire whatever your lead-alert switches and limits say: subscribing is the opt-in.

Payload

Every delivery is a JSON POST with the same envelope:

{
  "id": "evt_8c1f…",
  "type": "deal.stage_changed",
  "createdAt": 1790000000000,
  "data": {
    "deal": {
      "id": "deal_abc123",
      "title": "Annual plan",
      "value": 1200,
      "currency": "USD",
      "pipelineId": "owner_default",
      "stageId": "proposal-sent",
      "stage": "Proposal sent",
      "status": "open",
      "source": "manual",
      "listId": "list_abc123",
      "rowId": "3f6c1b0e2d…",
      "email": "[email protected]"
    },
    "fromStageId": "meeting-booked"
  }
}

id is the same for every delivery of one event, and is also sent as the X-MailInApp-Idempotency-Key header, so you can safely drop a redelivery you've already processed. createdAt is in epoch milliseconds.

To see the data of each event type, call GET /api/v1/events/sample or use Send test.

Verify the signature

Deliveries carry X-MailInApp-Timestamp and X-MailInApp-Signature headers, computed exactly like a project webhook's. Check them against the raw body with your signing secret, and reject timestamps more than a few minutes old. The project webhook docs have a ready-made Node.js function.

Retries

Your endpoint has 5 seconds to answer with a 2xx. Anything else (a timeout, an error status, a redirect) is a failure, and the delivery is retried automatically with backoff over several hours, with the same body and idempotency key, to the webhook's current URL. Answer quickly and do slow work afterwards.

  • If your endpoint answers 410 Gone, the webhook is disabled. That's how REST Hooks clients unsubscribe.
  • Revoking an API key disables the webhooks created with it.
  • Deleting a webhook stops its pending retries.

Subscribe over the API

Zapier, n8n and your own code can manage webhooks with an API key (Authorization: Bearer mia_live_…):

curl https://mailinapp.com/api/v1/webhooks \
  -H "Authorization: Bearer mia_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/mailinapp", "events": ["lead.hot", "booking.created"]}'

The 201 response holds the subscription's id and its secret, which signs every delivery. Keep it: it isn't returned again. GET /api/v1/webhooks lists your subscriptions, and DELETE /api/v1/webhooks/{id} removes one. One unknown event name rejects the whole request. These routes allow 30 requests a minute per key.

The full contract is in the OpenAPI spec.