Go SDK

Integrate Perly churn prevention into your Go application with standard net/http middleware and a typed user builder.

github.com/perly-ai/perly-gov1.0.05 minutes

Installation

go get github.com/perly-ai/perly-go

Usage

Wrap your HTTP handler with the Perly middleware. The middleware intercepts every request and resolves the current user via the resolver function.

// main.go
package main

import (
    "net/http"
    "os"

    perly "github.com/perly-ai/perly-go"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/", apiHandler)

    handler := perly.Middleware(mux, perly.Config{
        APIKey:   os.Getenv("PERLY_API_KEY"),
        Resolver: resolveUser,
    })

    http.ListenAndServe(":8080", handler)
}

The SDK works with any router that satisfies http.Handler, including Chi, Gorilla Mux, and the standard library.

User Resolver

The resolver is a function that extracts the authenticated user from the request and returns a *perly.User built with the builder pattern.

func resolveUser(r *http.Request) *perly.User {
    user := auth.UserFromContext(r.Context())
    if user == nil {
        return nil
    }

    return perly.NewBuilder().
        SetID(user.ID).
        SetMetadata(map[string]interface{}{
            "plan":       user.Plan,
            "region":     user.Region,
            "company_id": user.CompanyID,
        }).
        LinkStripeByID(user.StripeCustomerID).
        LinkHubspotByID(user.HubspotContactID).
        Build()
}

Tracking Events

Retrieve the Perly client from the request context to track customer engagement events.

func onboardingCompleteHandler(w http.ResponseWriter, r *http.Request) {
    client := perly.ClientFromContext(r.Context())
    user := auth.UserFromContext(r.Context())

    err := client.Track(user.ID, "onboarding_completed", nil)
    if err != nil {
        http.Error(w, "tracking failed", http.StatusInternalServerError)
        return
    }

    json.NewEncoder(w).Encode(map[string]bool{"success": true})
}

func exportReportHandler(w http.ResponseWriter, r *http.Request) {
    client := perly.ClientFromContext(r.Context())
    user := auth.UserFromContext(r.Context())

    client.Track(user.ID, "report_exported", map[string]interface{}{
        "format": r.URL.Query().Get("format"),
    })
}

Expansion Signals

Send signals when customers approach plan limits. Each signal includes relevant metadata for the expansion workflow.

func checkLimitsHandler(w http.ResponseWriter, r *http.Request) {
    client := perly.ClientFromContext(r.Context())
    user := auth.UserFromContext(r.Context())

    seats := getSeatCount(user.CompanyID)
    if seats.Current > int(float64(seats.Limit) * 0.9) {
        client.Signal(user.ID, "seat_limit_near", map[string]interface{}{
            "current": seats.Current,
            "limit":   seats.Limit,
        })
    }
}

// Other signal types
client.Signal(userID, "api_usage_high", map[string]interface{}{"current": 9500, "limit": 10000})
client.Signal(userID, "rate_limit_hit", map[string]interface{}{"endpoint": "/api/search"})
client.Signal(userID, "storage_limit_near", map[string]interface{}{"used_gb": 9.2, "limit_gb": 10})