Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

32 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SyncBridge

A multi-client middleware platform built with Symfony 7.4 LTS that receives Shopify webhooks, processes them asynchronously, normalises payloads per client, and syncs order data to downstream ERP/WMS systems.

Built as a hands-on Symfony learning project mirroring the architecture of real-world integration platforms.


Architecture Overview

Shopify (or any webhook source)
        │
        │  POST /webhook/order
        │  X-Shopify-Hmac-Sha256: <signature>
        ▼
┌─────────────────────────────┐
│      Nginx (port 8080)      │
└─────────────┬───────────────┘
              │
              ▼
┌─────────────────────────────┐
│     WebhookController       │
│  1. Verify HMAC signature   │
│  2. Check idempotency key   │
│  3. Persist WebhookEvent    │
│  4. Dispatch to queue       │
│  5. Return 202 immediately  │
└─────────────┬───────────────┘
              │
              │  Redis queue
              ▼
┌─────────────────────────────┐
│  OrderReceivedMessageHandler│  ← async worker
│  1. Resolve client config   │
│  2. Transform payload       │
│  3. POST to WMS/ERP         │
│  4. Update status           │
└─────────────────────────────┘
              │
              ▼
┌─────────────────────────────┐
│     REST API (/api/*)       │
│  GET /api/sync-events       │  ← authenticated, client-scoped
│  GET /api/sync-events/{id}  │
└─────────────────────────────┘

Tech Stack

Layer Technology
Framework Symfony 7.4 LTS
Language PHP 8.2
ORM Doctrine ORM
Queue Symfony Messenger + Redis
Database MySQL 8
Cache/Queue Transport Redis
Web Server Nginx
Runtime PHP 8.2-FPM
Containerisation Docker + Docker Compose
Testing PHPUnit 11
Logging Monolog

Project Structure

sync_bridge/
├── app/
│   ├── src/
│   │   ├── Command/               # Console commands
│   │   │   ├── GenerateWebhookSignatureCommand.php
│   │   │   ├── SyncRetryFailedCommand.php
│   │   │   └── SyncStatusCommand.php
│   │   ├── Controller/            # HTTP controllers
│   │   │   ├── MockWmsController.php
│   │   │   ├── SyncEventController.php
│   │   │   └── WebhookController.php
│   │   ├── Entity/                # Doctrine entities
│   │   │   └── WebhookEvent.php
│   │   ├── EventListener/         # Symfony event listeners
│   │   │   └── ApiAuthListener.php
│   │   ├── Integration/           # Per-client integration layer
│   │   │   ├── ClientAIntegration.php
│   │   │   ├── ClientBIntegration.php
│   │   │   ├── ClientIntegrationInterface.php
│   │   │   └── ClientIntegrationResolver.php
│   │   ├── Message/               # Messenger message classes
│   │   │   └── OrderReceivedMessage.php
│   │   ├── MessageHandler/        # Messenger handlers
│   │   │   └── OrderReceivedMessageHandler.php
│   │   ├── Repository/            # Doctrine repositories
│   │   │   └── WebhookEventRepository.php
│   │   ├── Security/              # Security services
│   │   │   ├── ApiTokenResolver.php
│   │   │   └── ShopifySignatureVerifier.php
│   │   └── Service/               # Domain services
│   │       ├── SyncBridgeLogger.php
│   │       └── WmsClient.php
│   ├── config/
│   │   ├── packages/
│   │   │   ├── messenger.yaml     # Queue transport + routing config
│   │   │   ├── monolog.yaml       # Logging channels
│   │   │   └── syncbridge.yaml    # API tokens config
│   │   └── services.yaml          # DI container config
│   ├── migrations/                # Doctrine migrations
│   └── tests/
│       ├── Functional/            # Full HTTP stack tests
│       └── Unit/                  # Isolated unit tests
├── docker/
│   ├── php/Dockerfile
│   └── nginx/default.conf
└── docker-compose.yml

Getting Started

Prerequisites

1. Clone the repository

git clone https://github.com/albertovalinocarro/sync_bridge.git
cd sync_bridge

2. Start the containers

docker-compose up -d --build

Four services start:

Container Description Port
syncbridge_php PHP 8.2-FPM + Symfony CLI 9000 (internal)
syncbridge_nginx Nginx reverse proxy 8080
syncbridge_mysql MySQL 8 3306
syncbridge_redis Redis 6379

3. Install dependencies

docker exec -it syncbridge_php bash
cd app
composer install

4. Configure environment

cp app/.env app/.env.local

Default .env is pre-configured for Docker. Key values:

DATABASE_URL="mysql://syncbridge:syncbridge@mysql:3306/syncbridge?serverVersion=8.0.0&charset=utf8mb4"
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages
SHOPIFY_WEBHOOK_SECRET=syncbridge_fake_shopify_secret_for_dev

5. Run migrations

docker exec -it syncbridge_php bash
cd app
php bin/console doctrine:database:create --if-not-exists
php bin/console doctrine:migrations:migrate

6. Verify

Visit http://localhost:8080 — Symfony welcome page confirms the stack is running.


Running Commands

All Symfony commands run inside the PHP container:

docker exec -it syncbridge_php bash
cd app

API Reference

Webhook Endpoint

Receive Order Webhook

POST /webhook/order
Content-Type: application/json
X-Shopify-Hmac-Sha256: <hmac-sha256-signature>

Request body:

{
  "event": "order/created",
  "client_id": "client_a",
  "order_id": 1001,
  "amount_cents": 5000,
  "currency": "EUR"
}

Responses:

Status Meaning
202 Accepted Webhook queued for processing
200 OK Duplicate — already received
400 Bad Request Invalid JSON payload
401 Unauthorized Invalid HMAC signature

Generating a valid signature (dev only):

php bin/console app:webhook:generate-signature '{"event":"order/created","client_id":"client_a","order_id":1001}'

Sync Events API

All endpoints require a Bearer token in the Authorization header.

Available tokens (dev):

Token Access
syncbridge_client_a_token_secret client_a events only
syncbridge_client_b_token_secret client_b events only
syncbridge_internal_token_secret All clients

List Sync Events

GET /api/sync-events
Authorization: Bearer <token>

Query parameters:

Parameter Type Description
client_id string Filter by client (internal token only)
status string Filter by status
limit int Results per page (max 100, default 20)
offset int Pagination offset

Event statuses:

Status Meaning
pending Persisted, awaiting async processing
processed Normalised, WMS sync not yet confirmed
synced Successfully synced to WMS/ERP
failed WMS sync failed — eligible for retry
skipped No integration found for client

Get Single Event

GET /api/sync-events/{id}
Authorization: Bearer <token>

Client tokens can only access their own events. Returns 404 for events belonging to other clients.


Console Commands

Sync Status Dashboard

php bin/console app:sync:status
php bin/console app:sync:status --client=client_a

Retry Failed Events

php bin/console app:sync:retry-failed
php bin/console app:sync:retry-failed --client=client_b
php bin/console app:sync:retry-failed --dry-run

Run the Worker

php bin/console messenger:consume async -vv
php bin/console messenger:consume async --limit=10 -vv

Multi-Client Architecture

Each client implements ClientIntegrationInterface:

interface ClientIntegrationInterface
{
    public function getClientId(): string;
    public function transform(array $payload): array;
}

To add a new client:

  1. Create src/Integration/ClientXIntegration.php
  2. Tag it in config/services.yaml:
    App\Integration\ClientXIntegration:
        tags: ['app.client_integration']
  3. Add an API token in config/packages/syncbridge.yaml
  4. No other changes needed — the resolver picks it up automatically

Security

Webhook Signature Verification

Every incoming webhook is verified using HMAC-SHA256:

X-Shopify-Hmac-Sha256: base64(hmac_sha256(raw_body, SHOPIFY_WEBHOOK_SECRET))

API Authentication

REST API endpoints use Bearer token authentication scoped per client.


Testing

php bin/phpunit --testdox

Test database setup (first time):

docker exec syncbridge_mysql mysql -u root -proot -e "CREATE DATABASE IF NOT EXISTS syncbridge_test; GRANT ALL PRIVILEGES ON syncbridge_test.* TO 'syncbridge'@'%'; FLUSH PRIVILEGES;"
php bin/console doctrine:migrations:migrate --env=test --no-interaction

Logging

tail -f var/log/syncbridge.log

All entries include webhook_event_id, client_id, and duration_ms for filtering and alerting.


Roadmap

  • Docker environment
  • Symfony 7.4 LTS scaffold
  • Webhook endpoint with HMAC verification
  • Doctrine entity + migrations
  • Symfony Messenger async processing
  • Multi-client integration layer
  • Outbound WMS sync via HttpClient
  • REST API with filtering and pagination
  • Idempotency — duplicate webhook detection
  • PHPUnit test suite — unit + functional
  • Console commands — status dashboard + retry
  • API token authentication — client-scoped
  • Structured logging — dedicated Monolog channel
  • Query optimisation — composite indexes
  • Supervisor config for production worker
  • Inventory sync webhook support
  • Fulfilment status callback webhook

License

MIT


Done by

Alberto Valiño Carro.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages