Cloud Webhooks Reference

In this reference guide, you'll learn how Medusa delivers webhook events to your application, and you'll find the details and payload of every event that Medusa sends.

Webhooks Overview#

A webhook is an HTTP request that Medusa sends to an endpoint you own when something happens in your organization, such as a build starting or a deployment finishing. Webhooks let you react to changes in Cloud without polling for them.

Medusa delivers events to the webhook endpoint configured for your organization. If your organization has no endpoint, or its endpoint is disabled, Medusa doesn't send any events.

Every event is a POST request with a JSON body. Your endpoint must respond with a 2xx status code within ten seconds, otherwise Medusa treats the delivery as failed and retries it. You can track events delivery in the Cloud dashboard.

Note: Refer to the Webhooks Changelog for dated updates to the webhook events that Medusa delivers.

Webhook Delivery Details#

Medusa wraps every event in the same envelope. The data property holds the event's payload, which differs per event:

Code
1{2  "id": "whev_01K2M7Q8ZCTVX3H4",3  "type": "deployment.created",4  "data": {5    // The payload of the event.6  },7  "created_at": "2026-08-07T09:14:22.000Z"8}
Loading...

Webhook Request Headers#

Every delivery includes the following headers:

Header

Description

X-Medusa-Event

The name of the event, matching the type property of the body.

X-Medusa-Delivery

The ID of the delivered event, matching the id property of the body.

X-Medusa-Signature

The signature of the request, which you use to verify that Medusa sent it. Refer to the Verify Webhook Signatures section for details.

User-Agent

Always MedusaCloud-Webhook/1.0.

Verify Webhook Signatures#

Medusa signs every request with the secret of your webhook endpoint, and sends the signature in the X-Medusa-Signature header:

Terminal
X-Medusa-Signature: t=1786785262,v1=5e9b41...

The header holds two values separated by a comma:

Value

Description

t

The time Medusa signed the request, as a Unix timestamp in seconds.

v1

The HMAC SHA-256 signature, in hexadecimal format.

To verify a request, compute the HMAC SHA-256 of the string {t}.{raw request body} using your endpoint's secret, then compare the result to v1.

For example:

In a Medusa application, verify the signature in a middleware so that your route only runs for requests that Cloud sent.

Start by registering the middleware on your webhook route in src/api/middlewares.ts:

src/api/middlewares.ts
1import { defineMiddlewares } from "@medusajs/framework/http"2import {3  verifyWebhookSignature,4} from "./utils/verify-webhook-signature"5
6export default defineMiddlewares({7  routes: [8    {9      method: ["POST"],10      matcher: "/cloud-webhooks",11      bodyParser: { preserveRawBody: true },12      middlewares: [verifyWebhookSignature],13    },14  ],15})

The preserveRawBody option of bodyParser stores the raw request body in req.rawBody, which you need to compute the signature. Learn more in the Configure Request Body Parser guide.

Then, create the middleware in src/api/utils/verify-webhook-signature.ts:

src/api/utils/verify-webhook-signature.ts
1import {2  MedusaNextFunction,3  MedusaRequest,4  MedusaResponse,5} from "@medusajs/framework/http"6import crypto from "crypto"7
8export function verifyWebhookSignature(9  req: MedusaRequest,10  res: MedusaResponse,11  next: MedusaNextFunction12) {13  const header = req.headers["x-medusa-signature"]14  const secret = process.env.CLOUD_WEBHOOK_SECRET15
16  if (typeof header !== "string" || !secret) {17    return res.sendStatus(401)18  }19
20  const parts = new URLSearchParams(21    header.replace(/,/g, "&")22  )23  const timestamp = parts.get("t")24  const signature = parts.get("v1")25
26  if (!timestamp || !signature || !req.rawBody) {27    return res.sendStatus(401)28  }29
30  const expected = crypto31    .createHmac("sha256", secret)32    .update(33      `${timestamp}.${req.rawBody.toString("utf8")}`,34      "utf8"35    )36    .digest("hex")37
38  if (39    expected.length !== signature.length ||40    !crypto.timingSafeEqual(41      Buffer.from(expected),42      Buffer.from(signature)43    )44  ) {45    return res.sendStatus(401)46  }47
48  next()49}

Your route then handles the event, knowing that the request is verified:

src/api/cloud-webhooks/route.ts
1import {2  MedusaRequest,3  MedusaResponse,4} from "@medusajs/framework/http"5
6export async function POST(7  req: MedusaRequest,8  res: MedusaResponse9) {10  const event = req.body as {11    id: string12    type: string13  }14
15  // TODO handle the event16
17  res.sendStatus(200)18}
Warning: Compute the signature from the raw request body. If your framework parses the body into an object before your handler runs, the signature won't match.

You can also compare t to the current time and reject requests that are older than the window you accept. For example:

Code
1export function isRecent(2  header: string,3  toleranceInSeconds = 3004) {5  const parts = new URLSearchParams(6    header.replace(/,/g, "&")7  )8  const timestamp = Number(parts.get("t"))9
10  if (!timestamp) {11    return false12  }13
14  const ageInSeconds = Date.now() / 1000 - timestamp15
16  return ageInSeconds < toleranceInSeconds17}

Medusa signs every delivery attempt at the time it sends it, so retries of the same event pass this check.

Webhook Retries#

Medusa attempts the first delivery as soon as it queues the event. If your endpoint doesn't respond with a 2xx status code within ten seconds, Medusa retries the delivery with an exponential backoff, starting at five minutes and doubling after every attempt.

Medusa will retry within twenty-four hours of the first attempt. After that, it stops retrying and marks the event as failed. You can track the status of deliveries in the Cloud dashboard.

A delivery fails when your endpoint responds with a status code outside the 2xx range, when it doesn't respond within ten seconds, or when Medusa can't reach it. Medusa doesn't follow redirects, so a 3xx response also counts as a failed delivery.


Webhook Events#

Medusa sends the following events:

Event

Description

build.canceled

The build was canceled.

build.created

Medusa created a build for an environment.

build.failed

A build failed.

build.succeeded

A build completed successfully.

deployment.canceled

The deployment was canceled.

deployment.created

Medusa created a deployment for an environment.

deployment.failed

A deployment failed.

deployment.succeeded

A deployment completed successfully.


Webhook Build Events#

Medusa sends these events related to an environment's builds, which happen after you push a commit to the environment's branch.

build.canceled#

Cloud delivers this event when a build is canceled.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:03:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF",9      "status": "canceled"10    },11    "environment": {12      "branch": "main",13      "handle": "production",14      "id": "projenv_01HXYZ7890ABCDEF",15      "name": "Production"16    },17    "organization": {18      "id": "org_01HXYZ9012ABCDEF",19      "name": "Acme Corp"20    },21    "project": {22      "handle": "my-storefront",23      "id": "proj_01HXYZ3456ABCDEF",24      "name": "My Storefront",25      "region": "eu-west-1",26      "repository": "https://github.com/acme/storefront"27    }28  },29  "id": "whev_01HXYZ4567ABCDEF",30  "type": "build.canceled"31}

build.created#

Cloud delivers this event when a new build is created for an environment.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:00:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF",9      "status": "created"10    },11    "environment": {12      "branch": "main",13      "handle": "production",14      "id": "projenv_01HXYZ7890ABCDEF",15      "name": "Production"16    },17    "organization": {18      "id": "org_01HXYZ9012ABCDEF",19      "name": "Acme Corp"20    },21    "project": {22      "handle": "my-storefront",23      "id": "proj_01HXYZ3456ABCDEF",24      "name": "My Storefront",25      "region": "eu-west-1",26      "repository": "https://github.com/acme/storefront"27    }28  },29  "id": "whev_01HXYZ1234ABCDEF",30  "type": "build.created"31}

build.failed#

Cloud delivers this event when a build fails.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:05:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF",9      "status": "failed"10    },11    "environment": {12      "branch": "main",13      "handle": "production",14      "id": "projenv_01HXYZ7890ABCDEF",15      "name": "Production"16    },17    "organization": {18      "id": "org_01HXYZ9012ABCDEF",19      "name": "Acme Corp"20    },21    "project": {22      "handle": "my-storefront",23      "id": "proj_01HXYZ3456ABCDEF",24      "name": "My Storefront",25      "region": "eu-west-1",26      "repository": "https://github.com/acme/storefront"27    }28  },29  "id": "whev_01HXYZ3456ABCDEF",30  "type": "build.failed"31}

build.succeeded#

Cloud delivers this event when a build completes successfully.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:05:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF",9      "status": "succeeded"10    },11    "environment": {12      "branch": "main",13      "handle": "production",14      "id": "projenv_01HXYZ7890ABCDEF",15      "name": "Production"16    },17    "organization": {18      "id": "org_01HXYZ9012ABCDEF",19      "name": "Acme Corp"20    },21    "project": {22      "handle": "my-storefront",23      "id": "proj_01HXYZ3456ABCDEF",24      "name": "My Storefront",25      "region": "eu-west-1",26      "repository": "https://github.com/acme/storefront"27    }28  },29  "id": "whev_01HXYZ2345ABCDEF",30  "type": "build.succeeded"31}

Webhook Deployment Events#

Medusa sends these events related to an environment's deployments, which happen after a build succeeds.

deployment.canceled#

Cloud delivers this event when a deployment is canceled.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:08:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF"9    },10    "deployment": {11      "id": "depl_01HXYZ6789ABCDEF",12      "status": "canceled"13    },14    "environment": {15      "branch": "main",16      "handle": "production",17      "id": "projenv_01HXYZ7890ABCDEF",18      "name": "Production"19    },20    "organization": {21      "id": "org_01HXYZ9012ABCDEF",22      "name": "Acme Corp"23    },24    "project": {25      "handle": "my-storefront",26      "id": "proj_01HXYZ3456ABCDEF",27      "name": "My Storefront",28      "region": "eu-west-1",29      "repository": "https://github.com/acme/storefront"30    }31  },32  "id": "whev_01HXYZ8901ABCDEF",33  "type": "deployment.canceled"34}

deployment.created#

Cloud delivers this event when a new deployment is created for an environment.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:06:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF"9    },10    "deployment": {11      "id": "depl_01HXYZ6789ABCDEF",12      "status": "created"13    },14    "environment": {15      "branch": "main",16      "handle": "production",17      "id": "projenv_01HXYZ7890ABCDEF",18      "name": "Production"19    },20    "organization": {21      "id": "org_01HXYZ9012ABCDEF",22      "name": "Acme Corp"23    },24    "project": {25      "handle": "my-storefront",26      "id": "proj_01HXYZ3456ABCDEF",27      "name": "My Storefront",28      "region": "eu-west-1",29      "repository": "https://github.com/acme/storefront"30    }31  },32  "id": "whev_01HXYZ5678ABCDEF",33  "type": "deployment.created"34}

deployment.failed#

Cloud delivers this event when a deployment fails.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:10:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF"9    },10    "deployment": {11      "id": "depl_01HXYZ6789ABCDEF",12      "status": "failed"13    },14    "environment": {15      "branch": "main",16      "handle": "production",17      "id": "projenv_01HXYZ7890ABCDEF",18      "name": "Production"19    },20    "organization": {21      "id": "org_01HXYZ9012ABCDEF",22      "name": "Acme Corp"23    },24    "project": {25      "handle": "my-storefront",26      "id": "proj_01HXYZ3456ABCDEF",27      "name": "My Storefront",28      "region": "eu-west-1",29      "repository": "https://github.com/acme/storefront"30    }31  },32  "id": "whev_01HXYZ7890ABCDEF",33  "type": "deployment.failed"34}

deployment.succeeded#

Cloud delivers this event when a deployment completes successfully.

Payload

Loading...

Example Payload

Code
1{2  "created_at": "2024-11-12T10:10:00.000Z",3  "data": {4    "build": {5      "commit_author": "Jane Doe",6      "commit_hash": "a1b2c3d4e5f6",7      "commit_message": "Add new product feature",8      "id": "build_01HXYZ5678ABCDEF"9    },10    "deployment": {11      "id": "depl_01HXYZ6789ABCDEF",12      "status": "succeeded"13    },14    "environment": {15      "branch": "main",16      "handle": "production",17      "id": "projenv_01HXYZ7890ABCDEF",18      "name": "Production"19    },20    "organization": {21      "id": "org_01HXYZ9012ABCDEF",22      "name": "Acme Corp"23    },24    "project": {25      "handle": "my-storefront",26      "id": "proj_01HXYZ3456ABCDEF",27      "name": "My Storefront",28      "region": "eu-west-1",29      "repository": "https://github.com/acme/storefront"30    }31  },32  "id": "whev_01HXYZ6789ABCDEF",33  "type": "deployment.succeeded"34}
Was this guide helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Medusa MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break