Skip to main content

Webhooks

Webhooks let ECOSIRE push real-time notifications to your server when events occur — no polling required. Configure your webhook URL in Dashboard → Settings → Webhooks.


Available Events

EventTrigger
checkout.completedA Stripe checkout session completes (order + licenses created)
invoice.paidA Stripe invoice is paid (subscription renewal)
subscription.deletedA customer cancels their subscription
subscription.updatedSubscription status changes (e.g., trial → active, active → past_due)
charge.refundedA charge is refunded (licenses auto-revoked)
payment.failedA payment attempt fails
license.activatedA license key is activated against a domain
license.revokedA license is manually revoked by an admin

Payload Format

Every webhook POST has:

  • Method: POST
  • Content-Type: application/json
  • Headers: X-Ecosire-Event, X-Ecosire-Signature, X-Ecosire-Delivery

Common envelope

{
"id": "evt_018e1234abcd70008000000000000001",
"event": "checkout.completed",
"createdAt": "2026-03-20T12:00:00.000Z",
"data": { ... }
}

Event Payloads

checkout.completed

{
"id": "evt_018e...",
"event": "checkout.completed",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"orderId": "018e1234-abcd-7000-8000-000000000010",
"customerId": "018e1234-abcd-7000-8000-000000000002",
"customerEmail": "[email protected]",
"amount": 49900,
"currency": "usd",
"lineItems": [
{
"productId": "018e...",
"productName": "ECOSIRE Shopify Connector",
"quantity": 1,
"unitAmount": 49900
}
],
"licenses": [
{
"licenseKey": "SHOP-XXXX-XXXX-XXXX-XXXX",
"productId": "018e...",
"activationsLimit": 3,
"expiresAt": "2027-03-20T12:00:00Z"
}
]
}
}

invoice.paid

{
"id": "evt_018e...",
"event": "invoice.paid",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"invoiceId": "inv_018e...",
"customerId": "018e...",
"customerEmail": "[email protected]",
"amount": 14900,
"currency": "usd",
"subscriptionId": "sub_1PxxxStripeId",
"periodStart": "2026-03-20T00:00:00Z",
"periodEnd": "2026-04-20T00:00:00Z"
}
}

subscription.updated

{
"id": "evt_018e...",
"event": "subscription.updated",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"subscriptionId": "sub_1PxxxStripeId",
"customerId": "018e...",
"previousStatus": "active",
"currentStatus": "past_due",
"licenses": [
{ "licenseKey": "SHOP-XXXX-XXXX-XXXX-XXXX", "action": "suspended" }
]
}
}

charge.refunded

{
"id": "evt_018e...",
"event": "charge.refunded",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"orderId": "018e...",
"chargeId": "ch_1PxxxStripeId",
"amount": 49900,
"currency": "usd",
"licenses": [
{ "licenseKey": "SHOP-XXXX-XXXX-XXXX-XXXX", "action": "revoked" }
]
}
}

license.activated

{
"id": "evt_018e...",
"event": "license.activated",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"licenseKey": "SHOP-XXXX-XXXX-XXXX-XXXX",
"domain": "mystore.myshopify.com",
"activationsCount": 2,
"activationsLimit": 3
}
}

license.revoked

{
"id": "evt_018e...",
"event": "license.revoked",
"createdAt": "2026-03-20T12:00:00Z",
"data": {
"licenseKey": "SHOP-XXXX-XXXX-XXXX-XXXX",
"revokedBy": "admin",
"reason": "Refund processed"
}
}

Signature Verification

Every webhook request includes an X-Ecosire-Signature header — a SHA256 HMAC of the raw request body signed with your webhook secret.

Always verify the signature before processing the event.

Node.js

import crypto from 'crypto';

function verifyWebhook(
rawBody: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// Express example
app.post('/webhooks/ecosire', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-ecosire-signature'] as string;
const secret = process.env.ECOSIRE_WEBHOOK_SECRET!;

if (!verifyWebhook(req.body.toString(), sig, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const event = JSON.parse(req.body.toString());
// process event...
res.json({ received: true });
});

Python

import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["ECOSIRE_WEBHOOK_SECRET"].encode()

@app.route("/webhooks/ecosire", methods=["POST"])
def handle_webhook():
sig = request.headers.get("X-Ecosire-Signature", "")
expected = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
# process event...
return {"received": True}

PHP

$secret = $_ENV['ECOSIRE_WEBHOOK_SECRET'];
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_ECOSIRE_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $sig)) {
http_response_code(401);
exit('Invalid signature');
}

$event = json_decode($body, true);

Retry Policy

If your endpoint does not return 2xx within 10 seconds, ECOSIRE marks the delivery as failed and retries:

AttemptDelay
1st retry5 minutes
2nd retry30 minutes
3rd retry2 hours

After 3 failed retries the delivery is permanently marked as failed. You can manually replay any failed delivery from Dashboard → Settings → Webhooks → Delivery Log.


Best Practices

  1. Respond immediately. Return 200 OK as soon as the signature is verified. Move heavy processing to a background queue (Bull, Celery, etc.).

  2. Idempotency. The same event may be delivered more than once (network retries). Use id (the event UUID) as an idempotency key — deduplicate in your DB before processing.

  3. Verify every request. Never skip signature verification — it is your only guarantee the request originated from ECOSIRE.

  4. Log raw payloads. Store the raw JSON body alongside the event ID for debugging and audit trails.

  5. Use HTTPS. Webhook endpoints must be HTTPS in production. HTTP endpoints are rejected.

  6. Separate concerns. Run one webhook handler per event type; avoid a monolithic switch/case that grows unbounded.


Testing Webhooks Locally

Use a tunneling tool to expose your local server:

# ngrok
ngrok http 3001

# Then configure the HTTPS URL in Dashboard → Settings → Webhooks
# e.g., https://a1b2c3d4.ngrok.io/webhooks/ecosire

Trigger a test delivery from the dashboard by clicking Send test event on any event type.