Detects duplicate invoices submitted over the WhatsApp Business API. Built with FastAPI, SQLAlchemy and pdfplumber.
This service receives invoice PDFs sent through WhatsApp Business, extracts the invoice data, computes a SHA-256 hash of the file, and rejects any document that has already been processed — preventing the same invoice from being used more than once.
Paper-based or manual invoice workflows are easy to abuse: a single invoice can be resubmitted multiple times, generating duplicate payments. Detecting duplicates reliably across WhatsApp submissions requires combining:
- file-level fingerprinting (so even a renamed file is caught),
- business-data extraction (invoice number, amount, date),
- and database-level integrity (unique constraints).
A small, layered FastAPI service that turns WhatsApp media into a duplicate-safe invoice record:
- WhatsApp user sends a PDF to the business number.
- The WhatsApp Business API calls the service's
POST /webhook. - The service downloads the media, computes a SHA-256 hash, and extracts
invoice_id,amountanddatewith pdfplumber. - If the invoice or hash already exists, the message is answered as duplicate; otherwise it is stored as valid.
- WhatsApp webhook with Meta verification (GET challenge) and message handling (POST).
- Duplicate detection via
UNIQUEconstraints on bothinvoice_idandpdf_hash— safe under concurrent requests. - PDF parsing supporting several invoice layouts (French and English formats).
- Multi-stage Dockerfile (builder + runtime) and a docker-compose stack with PostgreSQL 16.
- 19 unit tests covering PDF hashing, parsing, repository constraints and webhook routes.
- Config via environment variables (
.env.exampleprovided).
invoice-detector/
├── app/
│ ├── main.py # FastAPI entry point, lifespan, CORS, routers
│ ├── config.py # Environment-driven settings (pydantic-settings)
│ ├── database.py # SQLAlchemy engine & session
│ ├── schemas.py # Pydantic validation schemas
│ ├── models/
│ │ └── invoice.py # SQLAlchemy Invoice model
│ ├── repositories/
│ │ └── invoice_repository.py # Data access layer
│ ├── services/
│ │ └── invoice_service.py # Business orchestration
│ ├── routes/
│ │ ├── webhook.py # GET/POST /webhook
│ │ └── health.py # /health + /invoices
│ └── utils/
│ ├── pdf_utils.py # SHA-256 hash + PDF data extraction
│ └── whatsapp_client.py # WhatsApp Business API client
├── app/tests/test_invoice_system.py # 19 unit tests (pytest)
├── scripts/test_webhook.sh # Manual curl test script
├── examples/webhook_payload_example.json
├── Dockerfile # Multi-stage build
├── docker-compose.yml # API + PostgreSQL 16
├── requirements.txt
├── pyproject.toml
└── .env.example
WhatsApp User
│ 1. sends an invoice PDF
▼
WhatsApp Business API
│ 2. POST /webhook
▼
Invoice Detector API
├─ 3. download the PDF (WhatsApp Media API)
├─ 4. compute SHA-256 hash
├─ 5. extract invoice_id, amount, date
├─ 6. check for duplicates (UNIQUE constraints)
│
├─ ✅ new invoice → stored + "Facture valide" answer
└─ ❌ duplicate → "Facture déjà utilisée" answer
- Python 3.12, FastAPI, SQLAlchemy, Pydantic
- pdfplumber for PDF text extraction
- WhatsApp Business API (Meta Graph API v18)
- PostgreSQL 16 (SQLite for local/dev)
- pytest for tests · Docker / docker-compose
pip install -r requirements.txt
pytest -v19 tests cover PDF hashing (determinism, uniqueness), invoice parsing (full text, missing fields), repository behaviour (duplicate invoice_id, duplicate hash) and webhook routes (verification, invalid token, non-document payloads, PDF payload).
# Start API + PostgreSQL
docker-compose up --build
# Background
docker-compose up -d --build
# Stop
docker-compose downngrok http 8000Copy the generated HTTPS URL into the Meta for Developers dashboard as the webhook URL.
- Python 3.11+
- pip
git clone https://github.com/Blue-B-code/invoice-detector.git
cd invoice-detector
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then edit with your values
uvicorn app.main:app --reload --port 8000API: http://localhost:8000 · Swagger docs: http://localhost:8000/docs
| Variable | Description | Default |
|---|---|---|
APP_ENV |
development / production |
development |
DATABASE_URL |
SQLAlchemy database URL | sqlite:///./invoices.db |
WHATSAPP_API_URL |
WhatsApp Business API base URL | https://graph.facebook.com/v18.0 |
WHATSAPP_PHONE_NUMBER_ID |
Business phone number ID | (required in production) |
WHATSAPP_ACCESS_TOKEN |
Meta access token | (required in production) |
WHATSAPP_VERIFY_TOKEN |
Webhook verification token | invoice_detector_verify_token |
MAX_PDF_SIZE_MB |
Max PDF size in MB | 10 |
LOG_TO_FILE |
Write logs to logs/app.log |
false |
curl "http://localhost:8000/webhook?hub.mode=subscribe&hub.verify_token=invoice_detector_verify_token&hub.challenge=test123"curl -X POST http://localhost:8000/webhook \
-H "Content-Type: application/json" \
-d @examples/webhook_payload_example.jsoncurl http://localhost:8000/invoicesInvoice number: Facture N° : INV-2024-001, Invoice #: 12345, N° facture : FAC-001
Amount: Total : 1 250,00, Montant TTC : 500.00, Amount Due: 1,234.56
Date: 15/01/2024, 2024-01-15, 15 janvier 2024, 15 January 2024
- Async queue — Celery/ARQ + Redis for background processing
- OCR — Tesseract for scanned PDFs
- Multi-tenant — support multiple companies
- Dashboard — web UI to browse invoices
- Alerts — email/Slack notification on duplicate detection
MIT