Skip to content

PrestaShop Webhook Integration

This guide explains how to configure and use PrestaShop webhooks with OpenLinker. PrestaShop webhooks allow real-time synchronization of products, inventory, and orders.

  1. PrestaShop Installation: PrestaShop 1.7+ with webhook module installed
  2. OpenLinker Connection: Active PrestaShop connection configured in OpenLinker
  3. Webhook Secret: Shared secret configured in both PrestaShop and OpenLinker
Terminal window
POST /connections
{
"name": "My PrestaShop Store",
"platformType": "prestashop",
"adapterKey": "prestashop.webservice.v1",
"status": "active",
"credentials": {
"apiUrl": "https://your-store.com/api",
"apiKey": "your-api-key"
}
}

Save the connectionId from the response.

Set the webhook secret as an environment variable:

Terminal window
# Connection-specific (recommended)
export OPENLINKER_WEBHOOK_SECRET__PRESTASHOP__<CONNECTION_ID>=your-secret-key
# Or provider-level (fallback)
export OPENLINKER_WEBHOOK_SECRET__PRESTASHOP=your-secret-key

Note: Replace <CONNECTION_ID> with your actual connection ID (uppercase, no dashes).

Install the OpenLinker module in your PrestaShop instance:

For detailed installation instructions, see: PrestaShop Module README

Quick Start:

  1. Upload module ZIP to PrestaShop (or use bind-mount for development)
  2. Install module via PrestaShop backoffice: Modules → Module Manager
  3. Configure module settings (see module README for details)

In your PrestaShop webhook module configuration:

  • Webhook URL: https://your-openlinker-instance.com/webhooks/prestashop/<CONNECTION_ID>
  • Secret Key: Same secret as configured in OpenLinker
  • Events: Select events to subscribe to (see Supported Events below)

Triggered when a product is created or updated.

Payload:

{
"schemaVersion": 1,
"eventId": "prestashop-product-12345",
"eventType": "product.saved",
"occurredAt": "2025-01-01T12:00:00.000Z",
"object": {
"type": "product",
"externalId": "12345"
},
"payload": {
"name": "Product Name",
"reference": "PROD-001",
"price": 29.99
}
}

Result: Triggers master.product.syncByExternalId job to fetch full product data via PrestaShop WebService API.

Triggered when product stock level changes.

Payload:

{
"schemaVersion": 1,
"eventId": "prestashop-stock-12345",
"eventType": "stock.changed",
"occurredAt": "2025-01-01T12:00:00.000Z",
"object": {
"type": "stock",
"externalId": "12345"
},
"payload": {
"quantity": 100
}
}

Result: Triggers prestashop.inventory.syncByExternalId job to sync inventory levels.

Triggered when a new order is created.

Payload:

{
"schemaVersion": 1,
"eventId": "prestashop-order-67890",
"eventType": "order.created",
"occurredAt": "2025-01-01T12:00:00.000Z",
"object": {
"type": "order",
"externalId": "67890"
},
"payload": {
"total": 99.99,
"currency": "EUR"
}
}

Result: Triggers prestashop.order.syncByExternalId job to fetch full order data.

Triggered when order status changes.

Payload:

{
"schemaVersion": 1,
"eventId": "prestashop-order-status-67890",
"eventType": "order.status_changed",
"occurredAt": "2025-01-01T12:00:00.000Z",
"object": {
"type": "order",
"externalId": "67890"
},
"payload": {
"status": "shipped"
}
}

Result: Triggers prestashop.order.syncByExternalId job to sync order status.

PrestaShop webhook module must generate signatures using the same scheme as OpenLinker:

const crypto = require('crypto');
function generateSignature(secret, timestamp, rawBody) {
const signedPayload = timestamp + '.' + rawBody;
return crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
}
// Usage
const timestamp = Date.now().toString();
const rawBody = JSON.stringify(payload);
const signature = generateSignature(secret, timestamp, rawBody);
// Headers
headers['X-OpenLinker-Timestamp'] = timestamp;
headers['X-OpenLinker-Signature'] = `sha256=${signature}`;
  1. Generate signature:

    Terminal window
    node scripts/generate-webhook-signature.js your-secret-key
  2. Send test webhook:

    Terminal window
    curl -X POST http://localhost:3000/webhooks/prestashop/<CONNECTION_ID> \
    -H "Content-Type: application/json" \
    -H "X-OpenLinker-Timestamp: <TIMESTAMP>" \
    -H "X-OpenLinker-Signature: sha256=<SIGNATURE>" \
    -d '{
    "schemaVersion": 1,
    "eventId": "test-event-123",
    "eventType": "product.saved",
    "occurredAt": "2025-01-01T12:00:00.000Z",
    "object": {
    "type": "product",
    "externalId": "12345"
    }
    }'
  3. Verify:

    • Check application logs for “Published webhook event”
    • Check Redis stream: XREAD STREAMS events.inbound.webhooks 0
    • Check job queue: XREAD STREAMS jobs.sync 0

PrestaShop’s actionProductSave hook (and other hooks) can fire multiple times during a single product save operation. This is expected PrestaShop behavior, not a bug.

Common scenarios:

  • Product saved for multiple languages (2 languages = 2+ hook fires)
  • Product saved for multiple shops (multi-shop setup)
  • Product saved in multiple phases (main record, attributes, categories, stock, images, SEO)
  • Hook called from multiple places in PrestaShop core code

Example: Saving a product with 2 languages can trigger the hook 6 times within the same second, all for the same product ID.

The PrestaShop webhook module implements automatic deduplication to prevent duplicate events:

  1. Deterministic Event IDs: Event IDs are generated deterministically based on:

    • Provider + Connection ID + Event Type + Object Type + External ID + Time Window (rounded to nearest minute)
  2. Database-Level Deduplication: Uses INSERT IGNORE with a unique constraint on event_id:

    • If the same event ID is inserted multiple times, only the first insert succeeds
    • Subsequent inserts are silently ignored (no error, no duplicate)
  3. Time Window: Events within the same minute for the same object generate the same event ID, preventing duplicates from rapid hook fires.

Result: Even if a hook fires 6 times in one second, only 1 event is created and sent to OpenLinker.

Note: Time windows use PrestaShop server timezone. Events created in different minutes are correctly treated as separate events (this is expected behavior for legitimate separate save operations).

To verify deduplication is working:

-- Check for duplicate events (should return 0 rows)
SELECT event_id, COUNT(*) as count
FROM ps_openlinker_webhook_outbox
GROUP BY event_id
HAVING count > 1;
-- Check events for a specific product (should see only 1 per minute)
SELECT id, event_type, external_id, created_at
FROM ps_openlinker_webhook_outbox
WHERE external_id = '23' AND event_type = 'product.saved'
ORDER BY created_at DESC;

Expected: One event per product save operation, even if the hook fired multiple times.

Possible causes:

  • Invalid signature (check secret key matches)
  • Timestamp out of window (check system clock sync)
  • Signature computed on wrong body (must use exact raw bytes)

Solution: Verify signature generation matches OpenLinker’s scheme exactly.

Possible causes:

  • Connection ID doesn’t exist
  • Connection is disabled
  • Provider mismatch (URL says prestashop but connection is allegro)

Solution: Check connection exists and is active with correct platformType.

Possible causes:

  • Handler not running (check application logs)
  • Consumer group not created (check logs for “Created consumer group”)
  • Redis connection issues

Solution: Check application logs and Redis connectivity.

Possible causes:

  • Event ID generation changed (should be deterministic)
  • Time window logic issue
  • Database unique constraint missing

Solution:

  1. Verify event_id column has unique constraint: SHOW CREATE TABLE ps_openlinker_webhook_outbox;
  2. Check event IDs are deterministic (same inputs = same ID)
  3. Verify INSERT IGNORE is being used in OutboxRepository::enqueueEvent()
  1. Use Connection-Specific Secrets: Prefer connection-specific secrets over provider-level for better security isolation.

  2. Monitor Webhook Delivery: Set up alerts for signature verification failures and duplicate events.

  3. Idempotent Event IDs: Use deterministic event IDs (e.g., prestashop-product-{id}) to enable proper deduplication.

  4. Minimal Payloads: Keep webhook payloads minimal. Full data is fetched via adapter APIs during sync jobs.

  5. Error Handling: Implement retry logic in PrestaShop webhook module for transient failures (5xx responses).