Accept Payments with Paystack in Go
To accept Paystack payments in Go, create HTTP handlers that call the Paystack REST API using net/http. Initialize a transaction, redirect the user to Paystack checkout, and verify the payment in a callback handler before granting value.
Setup
mkdir paystack-go && cd paystack-go
go mod init paystack-go
Set your environment variable:
export PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
Request and Response Structs
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type InitRequest struct {
Email string "json:\"email\""
Amount int "json:\"amount\""
Reference string "json:\"reference\""
CallbackURL string "json:\"callback_url\""
}
type PaystackResponse struct {
Status bool "json:\"status\""
Message string "json:\"message\""
Data struct {
AuthorizationURL string "json:\"authorization_url\""
AccessCode string "json:\"access_code\""
Reference string "json:\"reference\""
} "json:\"data\""
}
type VerifyResponse struct {
Status bool "json:\"status\""
Data struct {
Status string "json:\"status\""
Amount int "json:\"amount\""
Currency string "json:\"currency\""
Customer struct {
Email string "json:\"email\""
} "json:\"customer\""
Reference string "json:\"reference\""
} "json:\"data\""
}
var paystackSecret = os.Getenv("PAYSTACK_SECRET_KEY")
Initialize a Transaction
func initializeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var input struct {
Email string "json:\"email\""
Amount float64 "json:\"amount\""
}
json.NewDecoder(r.Body).Decode(&input)
ref := fmt.Sprintf("order_%d", time.Now().UnixNano())
reqBody := InitRequest{
Email: input.Email,
Amount: int(input.Amount * 100),
Reference: ref,
CallbackURL: "http://localhost:8080/payment/callback",
}
body, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.paystack.co/transaction/initialize", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+paystackSecret)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
http.Error(w, "Failed to call Paystack", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var result PaystackResponse
json.NewDecoder(resp.Body).Decode(&result)
if !result.Status {
http.Error(w, result.Message, http.StatusBadRequest)
return
}
// TODO: Save order to database
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"authorization_url": result.Data.AuthorizationURL,
"reference": result.Data.Reference,
})
}
Handle the Callback
func callbackHandler(w http.ResponseWriter, r *http.Request) {
reference := r.URL.Query().Get("reference")
if reference == "" {
reference = r.URL.Query().Get("trxref")
}
if reference == "" {
http.Error(w, "Missing reference", http.StatusBadRequest)
return
}
req, _ := http.NewRequest("GET", "https://api.paystack.co/transaction/verify/"+reference, nil)
req.Header.Set("Authorization", "Bearer "+paystackSecret)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
http.Error(w, "Verification failed", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var result VerifyResponse
json.NewDecoder(resp.Body).Decode(&result)
if result.Status && result.Data.Status == "success" {
// TODO: Verify amount, mark order as paid
fmt.Fprintf(w, "Payment successful! %s %d", result.Data.Currency, result.Data.Amount/100)
return
}
http.Error(w, "Payment not successful", http.StatusBadRequest)
}
Main Function
func main() {
http.HandleFunc("/api/pay", initializeHandler)
http.HandleFunc("/payment/callback", callbackHandler)
fmt.Println("Server running on :8080")
http.ListenAndServe(":8080", nil)
}
Production Checklist
- Use a router. Consider gorilla/mux or chi for production routing.
- Set up webhooks. See Handle Paystack Webhooks in Go.
- HTTPS. Use TLS in production. Paystack requires HTTPS for callbacks.
- Validate amounts. Check against your database, not client input.
- Error handling. Handle all errors from HTTP calls and JSON parsing.
Key Takeaways
- ✓Go standard library net/http handles all Paystack API calls. No external packages required.
- ✓Store your Paystack secret key in environment variables. Access with os.Getenv.
- ✓Amounts are in kobo (NGN), pesewas (GHS), or cents. Multiply by 100 before sending.
- ✓Go structs with json tags map cleanly to Paystack API request and response shapes.
- ✓Always verify the transaction server-side after the redirect callback.
- ✓Go error handling makes it explicit when API calls fail.
Frequently Asked Questions
- Do I need a Paystack Go package?
- No. The standard library net/http handles all API calls. The Paystack API is a simple REST API that works well with Go structs and json encoding.
- Should I use Gin or Echo for Paystack integration?
- Any Go router works. The Paystack API calls are the same regardless of your router choice. Gin and Echo add convenience for middleware and routing but are not required.
- How do I handle concurrent requests in Go?
- Go handles concurrency automatically with goroutines. Each HTTP request runs in its own goroutine. Use database transactions or atomic operations to prevent race conditions in payment fulfillment.
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