Logo Lanfrica

HalxDocs/onceo-core

Domain:

digital infrastructure

Record type:

software
Creator:
Hal
Host:
onceo-core — Open-source payment webhook reconciliation for African providers (Paystack, Flutterwave, OPay, M-Pesa). Exactly-once delivery, HMAC verification, idempotent stores. # onceo-core > **one clean payment event, every rail, exactly once.** A thin, auditable reconciliation layer for payment webhooks from African payment providers. It sits on top of rails you already use — it never becomes a payment processor itself. --- ## Supported Providers | Provider | Body Integrity | Status | |----------|---------------|--------| | Paystack | HMAC-SHA512 over body | Shipped | | Flutterwave | **None** — see warning below | Shipped | | OPay | HMAC-SHA512 over body | Shipped | | M-Pesa (Daraja) | Pre-shared callback token (constant-time cmp) | Shipped | | Svix | HMAC-SHA256 over `svix-id.svix-timestamp.body` | Shipped | | Bachs | HMAC-SHA256 over `timestamp.body` (hex) | Shipped | ## Installation ``` go get github.com ``` ## Quick Start ```go package main import ( "errors" "io" "net/http" "os" onceo "github.com" "github.com" ) func webhookHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // Bound the body before reading: onceo.Process rejects payloads over // MaxBodySize (1 MiB), but you must not hand it an unbounded blob first. body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, onceo.MaxBodySize)) if err != nil { http.Error(w, "request body too large", http.StatusRequestEntityTooLarge) return } provider, _ := paystack.New(os.Getenv("PAYSTACK_SECRET_KEY")) store := onceo.NewMemoryStore() // or store/redis event, err := onceo.Process(ctx, provider, store, r.Header, body) if errors.Is(err, onceo.ErrInvalidSignature) { http.Error(w, "invalid signature", http.StatusForbidden) return } if errors.Is(err, onceo.ErrDuplicateEvent) { w.WriteHeader(http.StatusOK) // idempotent: already processed return } if err != nil { http.Error(w, "internal error", http.StatusInternalServerError) return } // event is the canonical, normalized payment event switch event.Status { case onceo.StatusSuccess: fulfillOrder(event.Reference, event.AmountMinor, …

Licenses