Quickstart
Send one signed test receipt to E-FACTS and confirm it was accepted. This guide uses the generic adapter, which works with any POS. If you're on Square, Clover or Stripe, read POS integrations instead.
1. Request credentials
During the public preview, email info@efacts.ca with:
- your business name and POS system
- the store IDs your POS will send (one per location). Receipts from unmapped stores are held until the store is mapped.
- your expected monthly receipt volume
You'll receive:
| Credential | Used for |
|---|---|
EFACTS_BASE_URL | The ingest host for your environment |
EFACTS_WEBHOOK_SECRET | Signing each request (HMAC-SHA256) |
Keep the secret server-side. Never ship it inside a browser, mobile app or POS client that customers can inspect.
2. Build a receipt
Save this as receipt.json. Money is in integer cents when amounts_in_cents is true.
{
"store_id": "STORE_001",
"transaction_id": "TXN_0001",
"timestamp": "2026-09-26T15:04:05Z",
"currency": "CAD",
"amounts_in_cents": true,
"subtotal": 1400,
"tax": 175,
"total": 1575,
"payment_token": "tok_from_your_processor",
"items": [
{ "sku": "GEN-001", "description": "Notebook", "quantity": 2, "unit_cents": 500 },
{ "sku": "GEN-002", "description": "Pen set", "quantity": 1, "unit_cents": 400 }
]
}
3. Sign and send it
The signature is the lowercase hex HMAC-SHA256 of the exact bytes you send, keyed with your secret.
export EFACTS_BASE_URL="https://…" # from step 1
export EFACTS_WEBHOOK_SECRET="…" # from step 1
SIG=$(openssl dgst -sha256 -hmac "$EFACTS_WEBHOOK_SECRET" -hex < receipt.json | awk '{print $NF}')
curl -sS -X POST "$EFACTS_BASE_URL/api/v1/webhooks/ingest/generic" \
-H "Content-Type: application/json" \
-H "X-Efacts-Signature: $SIG" \
--data-binary @receipt.json
Use --data-binary, not -d: -d strips newlines, which changes the bytes and breaks the signature.
4. Check the response
{ "success": true, "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
202 Accepted means the receipt was stored and queued. Normalization runs asynchronously, so keep the eventId for support requests. A 401 with "error": "invalid_signature" means the signature did not match. See Webhook signatures for the usual causes.
Next steps
- Map every field your POS has: Receipt payload
- Signing in Node.js and Python: Webhook signatures
- Limits and what happens past 5,000 receipts: Free self-serve tier