Bonaventure OgetoBy Bonaventure Ogeto|

Build Paystack Subscriptions in Go

To build Paystack subscriptions in Go, create a plan via the API, initialize a transaction with the plan code, handle subscription webhooks in your webhook handler, track status in your database, and provide an endpoint for cancellation.

Subscribe Handler

func subscribeHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Email    string "json:\"email\""
        PlanCode string "json:\"plan_code\""
    }
    json.NewDecoder(r.Body).Decode(&input)

    body, _ := json.Marshal(map[string]string{
        "email":        input.Email,
        "plan":         input.PlanCode,
        "callback_url": "http://localhost:8080/subscription/callback",
    })

    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")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        http.Error(w, "Failed", http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    var result PaystackResponse
    json.NewDecoder(resp.Body).Decode(&result)

    if result.Status {
        json.NewEncoder(w).Encode(map[string]string{
            "authorization_url": result.Data.AuthorizationURL,
        })
        return
    }

    http.Error(w, result.Message, http.StatusBadRequest)
}

Subscription Webhook Events

// In processEvent:
case "subscription.create":
    var sub struct {
        SubscriptionCode string "json:\"subscription_code\""
        EmailToken       string "json:\"email_token\""
        Customer         struct {
            Email string "json:\"email\""
        } "json:\"customer\""
        Plan struct {
            PlanCode string "json:\"plan_code\""
        } "json:\"plan\""
    }
    json.Unmarshal(data, &sub)
    // Save to database

case "invoice.payment_failed":
    var inv struct {
        Subscription struct {
            SubscriptionCode string "json:\"subscription_code\""
        } "json:\"subscription\""
    }
    json.Unmarshal(data, &inv)
    // Update status to past_due

Cancel Subscription

func cancelHandler(w http.ResponseWriter, r *http.Request) {
    // Get subscription from database for current user
    subCode := "SUB_xxx" // from database
    emailToken := "token_xxx" // from database

    body, _ := json.Marshal(map[string]string{
        "code":  subCode,
        "token": emailToken,
    })

    req, _ := http.NewRequest("POST", "https://api.paystack.co/subscription/disable",
        bytes.NewBuffer(body))
    req.Header.Set("Authorization", "Bearer "+paystackSecret)
    req.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    // Update database status to cancelled
    w.Write([]byte("Subscription cancelled"))
}

Subscription Middleware

func requireSubscription(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Check user subscription status from database
        // If not active, return 403
        // Otherwise, call next(w, r)
        next(w, r)
    }
}

// Usage:
http.HandleFunc("/premium", requireSubscription(premiumHandler))

Ship Payments Faster

Key Takeaways

  • Paystack handles recurring billing. Create plans once and subscribe customers.
  • Initialize with a plan parameter instead of amount. Paystack uses the plan price.
  • Webhook events drive subscription state. Handle subscription.create and invoice.payment_failed.
  • Store subscription_code and email_token for managing subscriptions.
  • Go middleware can gate handlers behind subscription checks.
  • Use database transactions for safe subscription status updates.

Frequently Asked Questions

Can I change subscription plans?
Paystack does not support direct plan changes. Cancel the current subscription and create a new one on the desired plan.
What happens when a renewal fails?
Paystack retries the charge. You receive invoice.payment_failed webhooks. Update the status to past_due and notify the customer.
How do I store the email_token?
The email_token comes in the subscription.create webhook. Store it in your database alongside the subscription_code. You need both for cancellation.

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