Webhooks — Overview
Webhooks allow your application to receive real-time HTTP notifications whenever events occur in the HappyColis platform. Instead of polling the API, you register an HTTPS endpoint and HappyColis pushes event payloads to it automatically.
How Webhooks Work
- You register a webhook endpoint (HTTPS URL) for a specific event type.
- When the event fires, HappyColis sends an HTTP POST request to your endpoint.
- Your server verifies the HMAC signature and processes the payload.
- Your server responds with
2xxto acknowledge receipt.
If delivery fails (non-2xx or timeout), HappyColis retries with exponential backoff.
Integration Types
Webhooks are scoped to an integration context. There are two integration types:
| Type | Description |
|---|---|
| Integration App | A private or third-party application registered in the HappyColis dashboard. Use integrationWebhookCreate / integrationWebhookRemove. |
| OAuth Client | An OAuth application acting on behalf of an organization using the authorization code flow. Use oauthWebhookCreate / oauthWebhookRemove. |
Both types use the same IntegrationWebhookInput and produce the same webhook payload format.
Available Webhook Events
Order Events
| Event | Description |
|---|---|
order/created | A new order has been created. |
order/updated | An order has been updated. |
order/completed | An order has reached the COMPLETED state. |
order/cancelled | An order has been cancelled. |
Delivery Order Events
| Event | Description |
|---|---|
delivery_order/created | A new delivery order has been created. |
delivery_order/updated | A delivery order has been updated. |
delivery_order/accepted | A fulfillment service has accepted a delivery order. |
delivery_order/refused | A fulfillment service has refused a delivery order. |
delivery_order/completed | A delivery order has been fully fulfilled. |
delivery_order/cancelled | A delivery order has been cancelled. |
delivery_order/shipped | A delivery order has been marked as shipped. |
Shipment Events
| Event | Description |
|---|---|
shipment/created | A new shipment has been created. |
shipment/completed | A shipment has been delivered or returned (terminal state). |
shipment/shipping_event | A new tracking event has been received from the carrier. |
Stock Events
| Event | Description |
|---|---|
stock/created | A new stock reference has been created. |
stock/updated | A stock reference has been updated. |
stock/adjusted | A stock quantity has been adjusted. |
stock/reserved | Stock has been reserved for an order. |
stock/released | Previously reserved stock has been released. |
stock/consumed | Stock has been consumed (deducted) during fulfillment. |
stock/deleted | A stock reference has been deleted. |
Product Events
| Event | Description |
|---|---|
product/created | A new product has been created. |
product/updated | A product has been updated. |
product/deleted | A product has been deleted. |
product/published | A product has been published. |
product/unpublished | A product has been unpublished. |
Variant Events
| Event | Description |
|---|---|
variant/created | A new product variant has been created. |
variant/updated | A variant has been updated. |
variant/deleted | A variant has been deleted. |
variant/linked | A variant has been linked to a bundle. |
variant/unlinked | A variant has been unlinked from a bundle. |
variant/published | A variant has been published. |
variant/unpublished | A variant has been unpublished. |
Location Events
| Event | Description |
|---|---|
location/created | A new location has been created. |
location/updated | A location has been updated. |
location/deleted | A location has been deleted. |
location/activated | A location has been activated. |
location/deactivated | A location has been deactivated. |
location/service_connected | A fulfillment service has been connected to a location. |
location/service_disconnected | A fulfillment service has been disconnected from a location. |
location/service_updated | A fulfillment service configuration has been updated. |
Transfer Order Events
| Event | Description |
|---|---|
transfer_order/created | A new transfer order has been created. |
transfer_order/updated | A transfer order has been updated. |
transfer_order/completed | A transfer order has been completed. |
transfer_order/cancelled | A transfer order has been cancelled. |
transfer_order/shipped | A transfer order has been marked as shipped. |
Webhook Message Format
HTTP Headers
Every webhook request includes the following headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-HappyColis-Hmac-SHA256 | HMAC-SHA256 signature of the raw request body |
X-HappyColis-Event | The event type (e.g. order/created) |
X-HappyColis-Delivery | A unique delivery UUID for deduplication |
Payload Body
json
{
"event": "order/created",
"organizationId": "org_123",
"occurredAt": "2024-01-15T10:30:00.000Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"externalReference": "EXT-2024-001",
"state": "DRAFT"
}
}| Field | Description |
|---|---|
event | The event type string |
organizationId | The organization that owns the resource |
occurredAt | ISO 8601 timestamp of when the event occurred |
data | The event payload — structure varies by event type |
HMAC Signature Verification
Always verify the X-HappyColis-Hmac-SHA256 header before processing a webhook payload. The signature is a hex-encoded HMAC-SHA256 of the raw request body using your webhook secret as the key.
js
import crypto from 'crypto';
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex'),
);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-happycolis-hmac-sha256'];
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body.toString());
console.log('Received event:', payload.event);
res.sendStatus(200);
});python
import hashlib
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-HappyColis-Hmac-SHA256', '')
if not verify_signature(request.get_data(), signature, os.environ['WEBHOOK_SECRET']):
abort(401)
payload = request.get_json()
print(f"Received event: {payload['event']}")
return '', 200php
<?php
function verifyWebhookSignature(string $rawBody, string $signature, string $secret): bool
{
$expected = hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HAPPYCOLIS_HMAC_SHA256'] ?? '';
if (!verifyWebhookSignature($rawBody, $signature, getenv('WEBHOOK_SECRET'))) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
echo 'Received event: ' . $payload['event'];
http_response_code(200);Best Practices
- Always verify the HMAC signature before processing any payload. Reject requests with an invalid or missing signature with HTTP 401.
- Respond quickly. Return
200 OKas soon as signature verification passes. Process the payload asynchronously (e.g. enqueue a background job) to avoid timeouts. - Handle duplicates. Use the
X-HappyColis-Deliveryheader as an idempotency key. The same event may be delivered more than once on retry. - Use HTTPS only. Webhook endpoints must use TLS. Plain HTTP endpoints are rejected.
- Store your webhook secret securely. Treat the webhook secret like a password — use environment variables or a secrets manager, never hard-code it.
- Monitor webhook health. Check the
healthfield on your webhook registration to detect repeated delivery failures.
Operations
| Operation | Type | Description |
|---|---|---|
| integration | Query | Fetch an integration and its webhooks |
| integrationWebhookCreate | Mutation | Subscribe to a webhook (integration app context) |
| integrationWebhookRemove | Mutation | Remove a webhook (integration app context) |
| oauthWebhookCreate | Mutation | Subscribe to a webhook (OAuth client context) |
| oauthWebhookRemove | Mutation | Remove a webhook (OAuth client context) |