# Guide to Akeneo Webhooks: Features and Best Practices

Akeneo webhooks notify your systems when product data changes in the PIM: a product or product model is created, updated, or removed. If you're syncing Akeneo to downstream systems, webhooks are how you react to catalog changes without polling.

This guide covers how Akeneo's Events API webhooks work, the events you'll handle, how to verify the `x-akeneo-request-signature`, the demanding delivery model (fast ack, no retries), and the best practices for production.

## What are Akeneo webhooks?

Akeneo PIM's Events API delivers HTTP POSTs to a single Request URL per connection, that one URL receives all event types, so you route server-side. Each delivery batches events in an `events` array and is signed with an `x-akeneo-request-signature` header.

This is the Events API (marked deprecated in favor of the newer Event Platform, but still the webhook scheme this source targets). It's a demanding delivery model: acknowledge in under 500 ms, and there are no retries.

## Akeneo webhook features

| Feature | Details |
| --- | --- |
| Configuration | Per connection in PIM settings (one Request URL receives all events) |
| Signature headers | `x-akeneo-request-signature`, `x-akeneo-request-timestamp` (unix seconds) |
| Signature scheme | hex HMAC-SHA256 over `{timestamp}.{body}`, keyed with the connection secret |
| Freshness | Reject if `now - timestamp > 300` (5 minutes) |
| Payload | Batched `events` array (`action`, `event_id`, `event_datetime`, `author`, `data.resource`) |
| Acknowledgement | 2xx in under 500 ms |
| Retries | None, events dropped after ~2h; up to ~40k events/hr; order not guaranteed |
| SDK | None |

## Common events

Only product and product-model events exist in the Events API:

| Event | Fires when |
| --- | --- |
| `product.created` | A product is created |
| `product.updated` | A product is updated |
| `product.removed` | A product is removed |
| `product_model.created` | A product model is created |
| `product_model.updated` | A product model is updated |

Note: category events are not in this API, they exist only in the newer CloudEvents-based Event Platform. Route on each event's `action` in the batch.

## Setting up Akeneo webhooks

Enable the Events API per connection in the PIM connection settings and set the single Request URL. The connection secret is the HMAC key. Because one URL receives all events, dispatch by `action` server-side.

## Securing Akeneo webhooks

Each delivery carries `x-akeneo-request-signature` and `x-akeneo-request-timestamp`. To verify, build `{timestamp}.{body}`, compute an HMAC-SHA256 with the connection secret, hex-encode it, and compare against the signature. Also reject deliveries where `now - timestamp > 300` seconds. Verify against the raw body.

```javascript
const crypto = require("crypto");

const SECRET = process.env.AKENEO_CONNECTION_SECRET;

function verify(rawBody, timestamp, signature) {
  // 5-minute freshness (timestamp is unix seconds)
  if (Math.floor(Date.now() / 1000) - Number(timestamp) > 300) return false;

  const message = `${timestamp}.${rawBody.toString()}`;
  const expected = crypto.createHmac("sha256", SECRET).update(message).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.headers["x-akeneo-request-timestamp"];
  if (!verify(req.body, timestamp, req.headers["x-akeneo-request-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // MUST be under 500ms -- do not process here
  const { events } = JSON.parse(req.body);
  for (const event of events) processQueue.add(event); // route by event.action
});

```

The same check in Python:

```python
import hashlib
import hmac
import os
import time

SECRET = os.environ["AKENEO_CONNECTION_SECRET"].encode()

def verify(raw_body: bytes, timestamp: str, signature: str) -> bool:
    if int(time.time()) - int(timestamp) > 300:
        return False
    message = (timestamp + "." + raw_body.decode()).encode()
    expected = hmac.new(SECRET, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

```

## Akeneo webhook limitations and pain points

### No retries, and a sub-500ms ack

The Problem: You must return 2xx in under 500 ms, and there are no retries, events are dropped after ~2 hours if not accepted. Any slow processing in the handler loses events.

Why It Happens: The Events API prioritizes high throughput (up to ~40k events/hr) over retry guarantees.

Workarounds:

* Verify, enqueue, and return 2xx immediately, do zero processing in the request path.
* Reconcile via the API for anything you can't afford to miss, since there are no retries.

How Hookdeck Can Help: Hookdeck accepts deliveries in milliseconds and durably queues them, so the sub-500ms window and the no-retry model stop being a risk, your worker processes at its own pace with retries you control.

### One URL for all events

The Problem: A single Request URL per connection receives every event type, so you must dispatch server-side. A handler expecting one event type gets everything.

Why It Happens: Akeneo delivers all events to one endpoint per connection.

Workarounds:

* Route by each event's `action` in the batch.

How Hookdeck Can Help: Hookdeck's filters route by `action`, so each downstream handler receives only the events it cares about.

### Batched, unordered events

The Problem: Events arrive batched in `events[]` and delivery order is not guaranteed, so per-event and ordering assumptions break.

Why It Happens: The Events API batches and parallelizes for throughput.

Workarounds:

* Iterate the batch, treat each event independently, and order by `event_datetime` if needed.

How Hookdeck Can Help: Hookdeck can split batches and replay events in a controlled order during recovery.

### Only product/product_model events

The Problem: Category (and other) events aren't in this API; expecting them here means they never arrive.

Why It Happens: The Events API covers only product and product-model events; the rest are in the newer Event Platform.

Workarounds:

* Use this API for product events and the Event Platform for others.

How Hookdeck Can Help: Hookdeck can ingest both APIs and normalize them into one stream.

## Best practices

### Verify over `timestamp.body` with a 5-minute window

Build `{timestamp}.{body}`, HMAC-SHA256 with the connection secret, hex, compare in constant time, and reject stale timestamps (>300s).

### Acknowledge in under 500ms, process asynchronously

Verify, enqueue, and return 2xx immediately, no processing in the request path. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Route by action and reconcile

Dispatch by each event's `action`, and reconcile via the API since there are no retries.

## Conclusion

Akeneo's Events API webhooks are signed with an `x-akeneo-request-signature` HMAC over `{timestamp}.{body}` (5-minute freshness), delivered as batched product/product_model events to one URL per connection, with a sub-500ms ack and no retries. Verify fast, acknowledge immediately, route by `action`, and reconcile since dropped events aren't retried.

[Hookdeck](https://hookdeck.com) absorbs the sub-500ms window, adds the retries and durability Akeneo's Events API lacks, and verifies signatures at the edge, so your app processes verified product events without losing any.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Akeneo webhooks reliably in minutes.