ADR-0003. MQ-Pay payment events arrive via HTTP webhook, not polling
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-02-15 |
| Deciders | sale-team, payment-team |
| Supersedes | — |
Context
- After a customer completes payment via MQ-Pay (which abstracts VNPay et al.), sale needs to update order status, allocation usage, customer points, and emit Kafka for downstream services.
- Two ways for sale to learn the payment outcome:
- Poll MQ-Pay periodically.
- Webhook — MQ-Pay POSTs to sale.
- Polling is wasteful and high-latency. Real-time POS UX needs instant status update.
Decision
MQ-Pay POSTs to POST /v1/api/sale/webhooks/payment with event types mq-pay:attempt.{success,failed,expired,cancelled} and mq-pay:transaction.{settled,cancelled}. Sale processes the event, transitions status, and emits KafkaTopics.PAYMENT_SUCCESS post-commit.
Sale does not poll MQ-Pay. The webhook is the only inbound payment signal.
The webhook endpoint has no authentication. Trust is enforced by Cilium network policy: only MQ-Pay can reach /webhooks/payment over the internal network.
Consequences
| Pros | Cons |
|---|---|
| Real-time order status (sub-second after payment) | Single point of failure — if webhook fails to reach sale, status is stuck |
| No wasteful polling | Webhook security depends on network policy, not auth header |
| MQ-Pay retries 5xx automatically | Sale must be idempotent under retry (handler short-circuits if already at target status) |
| Decouples sale from payment provider quirks | Trust boundary is implicit; new operators must understand it |
Recovery
- If webhook fails repeatedly, MQ-Pay queues the event and retries.
- Operators can manually re-trigger redelivery via MQ-Pay admin.
- Sale's idempotency means re-deliveries are safe.
Alternatives Considered
| Option | Pros | Cons | Why rejected |
|---|---|---|---|
| Poll MQ-Pay every N seconds | Pull model; sale controls timing | Wasteful; high latency; doesn't scale to thousands of orders | UX too slow for POS |
| Hybrid: webhook + safety-net poll for stuck orders | Resilient | Adds complexity; double-event risk | Can be added later if webhook proves unreliable |
| Webhook with shared-secret auth | Stronger security | Operational burden of secret rotation; MQ-Pay already in trusted network | Network policy is sufficient given current threat model |
References
sale/src/common/webhook-types.ts:9(PaymentWebhookEventTypes)sale/src/controllers/payment-webhook/payment-webhook.controller.tssale/src/services/payment-webhook.service.ts(dispatcher)sale/src/services/sale-order-payment-webhook.service.ts(event handlers)