ecomone developers
Open Ecomone
Docs / Webhooks

Webhooks

Ecomone sends an HTTPS POST to your endpoint when something happens in a shop, so you don't have to poll.

Set up a webhook#

The merchant adds webhooks in the backend under Settings › Automations: a name, your https URL, the events and an on/off switch. A shop can have up to 10 webhooks. Each webhook has its own signing secret, which the merchant copies to you (it can be rotated). The backend can also send a test delivery ("test": true).

Events#

EventWhen it is sentPayload
order.paidAn order was paid successfully.order
order.refundedOnce per successful refund: refunds made in the Ecomone backend or through MCP, refunds made by a cancellation (including PATCH /orders/{id} with action: "cancel"), and refunds made directly in Stripe. Every partial refund is a separate event. The payload has an extra refund object.order + refund
order.cancelledAn order was cancelled in the Ecomone backend, through MCP or with PATCH /orders/{id} (action: "cancel"). Not sent if the refund of the cancellation fails.order
order.shippedThe order’s fulfillment status changed to shipped — through the API, the Ecomone backend or MCP. A later re-shipment (new tracking number) is delivered as a new event.order
order.fulfilledThe order’s fulfillment status changed to fulfilled — through the API, the Ecomone backend or MCP. A later re-shipment (new tracking number) is delivered as a new event.order
digital.deliveredSent right after order.paid when the order grants access to digital content.order
newsletter.subscribedSomeone subscribed to the shop’s newsletter.subscriber

Request format#

HeaderValue
Content-Typeapplication/json
User-AgentEcomone-Webhooks/1
X-Ecomone-Event<event>
X-Ecomone-Event-Id<event_id>
X-Ecomone-Signature<hex HMAC-SHA256 of the raw body>
POST your-endpoint — order.paid
{
  "event": "order.paid",
  "event_id": "order.paid:9f1c2b7e-4a55-4d1f-9b62-1c0a3f6d8e21",
  "created_at": "2026-08-28T09:14:25.000Z",
  "data": {
    "order": {
      "id": "9f1c2b7e-4a55-4d1f-9b62-1c0a3f6d8e21",
      "number": 1042,
      "status": "paid",
      "fulfillment_status": "unfulfilled",
      "tracking_number": null,
      "tracking_carrier": null,
      "tracking_url": null,
      "shipped_at": null,
      "created_at": "2026-08-28T09:13:58.000Z",
      "paid_at": "2026-08-28T09:14:22.000Z",
      "refunded_at": null,
      "cancelled_at": null,
      "amount_cents": 4900,
      "shipping_cents": 0,
      "currency": "CHF",
      "is_direct_sale": true,
      "direct_offer": {
        "id": "11111111-1111-4111-8111-111111111111",
        "title": "Online course summer offer",
        "slug": "summer-offer"
      }
    },
    "customer": {
      "email": "jane.doe@example.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "name": "Jane Doe",
      "address": {
        "line1": "Example Street 1",
        "line2": null,
        "postal_code": "8000",
        "city": "Zurich",
        "state": null,
        "country": "CH",
        "company": null,
        "phone": "+41 79 000 00 00"
      },
      "company": null,
      "vat_id": null,
      "vat_id_status": null
    },
    "items": [
      {
        "product_id": "22222222-2222-4222-8222-222222222222",
        "title": "Online course: First steps",
        "variant": null,
        "sku": "COURSE-001",
        "quantity": 1,
        "unit_price_cents": 4900,
        "line_total_cents": 4900,
        "personalization": [],
        "personalization_surcharge_cents": 0
      }
    ]
  }
}

Payloads#

Every delivery has the same envelope: event, event_id (stable across retries — use it to de-duplicate), created_at, data and test. Order events contain data.order, data.customer and data.items. Payment details and download links are never included.

newsletter.subscribed — data
{
  "subscriber": {
    "email": "string (lower-case)",
    "first_name": "string|null",
    "source": "string|null",
    "subscribed_at": "timestamp"
  }
}

Verify the signature#

X-Ecomone-Signature is the HMAC-SHA256, hex-encoded, over the exact raw request body (UTF-8). Compute the HMAC with your webhook secret over the raw body before parsing JSON, and compare in constant time.

Verify
import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/ecomone/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto.createHmac("sha256", process.env.ECOMONE_WEBHOOK_SECRET)
    .update(req.body) // raw Buffer
    .digest("hex");
  const given = req.get("X-Ecomone-Signature") ?? "";
  const ok = given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  // de-duplicate on event.event_id, then process asynchronously
  res.status(200).end();
});
i

The signature does not include a timestamp. Protect against replays by storing processed event_ids.

Delivery & retries#

  • Any 2xx response counts as delivered. Respond within 10 seconds.
  • 5 retries after 1, 5, 30, 120 and 360 minutes (6 attempts total), processed by a once-per-minute job.
  • After 20 consecutive deliveries that exhausted all attempts, the webhook is deactivated and the shop owner gets an in-app notification. Re-activating resets the counter.
  • Order of deliveries is not guaranteed. Each event is delivered at most once per webhook and event_id.
  • Delivery logs are kept for 30 days and visible to the merchant.

Best practices#

  • Acknowledge fast with 200, then process in a queue.
  • Fetch the latest state from the REST API (for example GET /orders/{id}) if you need more fields.
  • Make handlers idempotent: retries reuse the same event_id.

Authorize requests

Requests go straight from your browser to https://api.ecomone.shop. Your credentials stay in this browser tab and are never sent to this documentation site.