Skip to content
Developer Preview — APIs and language features may change before 1.0

Surfaces: Webhooks

The webhook surface turns a machine into a receiver for external events. When Stripe sends a payment notification, GitHub sends a push event, or Microsoft Graph sends a mail notification, the webhook surface receives it, validates the signature, and runs the machine.

Declaring a webhook surface

machine stripe_handler
accepts
event_type as text, is required
data as map, is required
responds with
status as text
action_taken as text
ensures
permissions
allowed to
reason, http
implements
decide route_event
when input.event_type is "payment_intent.succeeded"
ask process_payment, using: "anthropic:claude-haiku-4-5"
with task "Summarize this payment and decide next actions"
returns
status as text
action_taken as text
assuming
status: "processed"
action_taken: "receipt sent"
when input.event_type is "charge.failed"
compute handle_failure
{status: "failed", action_taken: "alert sent to billing team"}
otherwise
compute skip
{status: "ignored", action_taken: "none"}
expresses
webhook
path: "/hooks/stripe"
authentication: "hmac_sha256"
provider: "stripe"

Configuration options

ConfigRequiredDefaultDescription
pathYes-URL path for the webhook receiver
authenticationNo"none"Signature verification: "hmac_sha256", "none"
providerNo-Named provider for pre-configured signature verification

Providers

When you specify a provider, mashin knows the signature format:

ProviderSignature headerAlgorithm
"stripe"Stripe-SignatureHMAC-SHA256 with timestamp
"github"X-Hub-Signature-256HMAC-SHA256
"microsoft"Client state validationToken-based
"slack"X-Slack-SignatureHMAC-SHA256 with timestamp

For providers not in this list, use authentication: "hmac_sha256" and configure the secret in your cell’s credentials.

Registering webhooks with external services

After declaring a webhook surface, you need a public URL for the external service to deliver to. Two options:

Cloud cell

If your cell is deployed to mashin.live, the webhook URL is:

https://myorg.mashin.live/hooks/stripe

Local cell with tunnel

For local development, the tunnel provides a public URL:

Terminal window
mashin tunnel stripe_handler.mashin

The CLI shows the public URL. Use it when registering the webhook with the external service.

Automatic registration example

A machine can register its own webhook URL using context.cell.webhook_urls:

machine outlook_bridge
accepts
event as map
expresses
webhook
path: "/outlook"
provider: microsoft
implements
ask register, from: "@mashin/actions/http/post"
url: "https://graph.microsoft.com/v1.0/subscriptions"
body: {
changeType: "created",
notificationUrl: context.cell.webhook_urls["/outlook"],
resource: "me/mailFolders('Inbox')/messages",
expirationDateTime: "2026-06-01T00:00:00Z"
}
returns
subscription_id as text
assuming
subscription_id: "sub_123"

context.cell.webhook_urls resolves to the correct URL regardless of whether the cell is local (tunnel URL) or cloud (direct URL).

Payload mapping

The webhook payload is mapped to the machine’s accepts section. The mapping depends on the provider:

Stripe

// Incoming webhook payload
{
"type": "payment_intent.succeeded",
"data": {
"object": {
"amount": 5000,
"currency": "usd"
}
}
}

Maps to:

  • event_type = "payment_intent.succeeded"
  • data = {"object": {"amount": 5000, "currency": "usd"}}

GitHub

// Incoming webhook payload (with X-GitHub-Event: push header)
{
"ref": "refs/heads/main",
"commits": [...]
}

The X-GitHub-Event header value is available as context.webhook.event_type.

Generic

For webhooks without a named provider, the entire request body is passed as input. Define your accepts to match the expected payload shape.

Signature verification

When authentication: "hmac_sha256" is set, the webhook handler:

  1. Reads the signature from the provider-specific header
  2. Computes HMAC-SHA256 of the raw request body using the webhook secret from cell credentials
  3. Compares (constant-time) the computed signature with the received signature
  4. Rejects the request with HTTP 401 if the signature does not match

Set the webhook secret in your cell:

Terminal window
mashin secrets set stripe_webhook_secret whsec_...

Multiple webhook sources

A machine can declare multiple webhook surfaces:

expresses
webhook
path: "/hooks/stripe"
provider: "stripe"
webhook
path: "/hooks/github"
provider: "github"

Or a single machine can handle multiple event types from one source using decide steps to route by event type.

Governance

Every webhook delivery is governed:

  1. Signature is verified (if configured)
  2. Payload is validated against the accepts contract
  3. Governance permissions are checked
  4. The machine executes
  5. A SurfaceAccess event is recorded with :webhook surface
  6. The external service receives an HTTP 200 acknowledgment

Rejected webhooks return HTTP 403 (governance denied) or HTTP 401 (signature invalid). Most webhook providers retry on non-2xx responses.

Next steps