Verify Paystack Payments in Go
To verify a Paystack payment in Go, make a GET request to the verify endpoint, decode the JSON response into a typed struct, check status, amount, and currency, and use an atomic database update to prevent double-granting.
Verification Function
func verifyTransaction(reference string) (*VerifyResponse, error) {
req, err := http.NewRequest("GET",
"https://api.paystack.co/transaction/verify/"+reference, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+paystackSecret)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result VerifyResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func verifyAndFulfill(reference string, db *sql.DB) error {
result, err := verifyTransaction(reference)
if err != nil {
return fmt.Errorf("verification failed: %w", err)
}
if !result.Status || result.Data.Status != "success" {
return fmt.Errorf("payment not successful: %s", result.Data.Status)
}
// TODO: Check amount and currency against database
// Atomic update
res, err := db.Exec(
"UPDATE orders SET paid = true, paid_at = NOW() WHERE reference = $1 AND paid = false",
reference,
)
if err != nil {
return err
}
rows, _ := res.RowsAffected()
if rows == 0 {
// Already fulfilled
return nil
}
// Deliver value
return nil
}
Retry Logic
func verifyWithRetry(reference string, maxRetries int) (*VerifyResponse, error) {
for attempt := 0; attempt < maxRetries; attempt++ {
result, err := verifyTransaction(reference)
if err == nil {
return result, nil
}
if attempt < maxRetries-1 {
time.Sleep(time.Duration(1<
Verify Handler
func verifyHandler(w http.ResponseWriter, r *http.Request) {
reference := r.URL.Query().Get("reference")
if reference == "" {
http.Error(w, "Missing reference", http.StatusBadRequest)
return
}
err := verifyAndFulfill(reference, db)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
Key Takeaways
- ✓Go structs with json tags provide type-safe Paystack response parsing.
- ✓Always check status, amount, and currency against your database records.
- ✓Use SQL UPDATE with a WHERE clause on paid=false for atomic idempotent fulfillment.
- ✓Go error handling makes verification failures explicit and easy to trace.
- ✓Create a reusable VerifyAndFulfill function for use in callbacks and webhooks.
- ✓Retry with exponential backoff using time.Sleep for network failures.
Frequently Asked Questions
- Can I verify a transaction multiple times?
- Yes. The Paystack verify endpoint is idempotent and returns the same result every time.
- Should I use context.Context for verification?
- Yes. In production, use http.NewRequestWithContext to respect request cancellation and timeouts.
- How do I handle verification in goroutines?
- The atomic SQL UPDATE with WHERE paid=false ensures only one goroutine fulfills the order, even if multiple run concurrently.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon