HelloJohn / docs
Sdks

Go SDK

Use the HelloJohn Go SDK to verify tokens and manage users, tenants, and sessions from your Go backend.

Go SDK

github.com/hellojohn/hellojohn-go is the official Go SDK for HelloJohn. It provides token verification, user management, and full access to the admin API.

Installation

go get github.com/hellojohn/hellojohn-go

Setup

package main

import (
    "github.com/hellojohn/hellojohn-go"
)

func main() {
    client, err := hellojohn.NewClient(hellojohn.Config{
        SecretKey: os.Getenv("HJ_SECRET_KEY"),
        // Optional: for self-hosted
        APIURL: os.Getenv("HJ_API_URL"),
    })
    if err != nil {
        log.Fatal(err)
    }
}

Token verification

import (
    "context"
    "net/http"
    "strings"

    "github.com/hellojohn/hellojohn-go"
)

// HTTP middleware
func AuthMiddleware(client *hellojohn.Client) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            token := strings.TrimPrefix(authHeader, "Bearer ")
            payload, err := client.VerifyToken(r.Context(), token)
            if err != nil {
                http.Error(w, "Unauthorized", http.StatusUnauthorized)
                return
            }

            // Store payload in context
            ctx := hellojohn.WithPayload(r.Context(), payload)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Access payload in a handler
func GetMe(w http.ResponseWriter, r *http.Request) {
    payload := hellojohn.GetPayload(r.Context())
    json.NewEncoder(w).Encode(map[string]any{
        "user_id":   payload.Sub,
        "email":     payload.Email,
        "tenant_id": payload.TenantID,
        "role":      payload.Role,
    })
}

Token payload struct

type TokenPayload struct {
    Sub            string                 `json:"sub"`
    Email          string                 `json:"email"`
    EmailVerified  bool                   `json:"email_verified"`
    TenantID       string                 `json:"tenant_id"`
    OrganizationID string                 `json:"organization_id"`
    Role           string                 `json:"role"`
    CustomClaims   map[string]interface{} `json:"custom_claims"`
    IssuedAt       int64                  `json:"iat"`
    ExpiresAt      int64                  `json:"exp"`
}

User management

// List users
users, err := client.Users.List(ctx, &hellojohn.UserListParams{
    TenantID: "tnt_01H...",
    Limit:    50,
})

// Get a user
user, err := client.Users.Get(ctx, "usr_01H...")

// Create a user
newUser, err := client.Users.Create(ctx, &hellojohn.CreateUserParams{
    Email:       "alice@example.com",
    Password:    "strongpassword",
    DisplayName: "Alice",
    TenantID:    "tnt_01H...",
    Role:        "member",
})

// Update a user
updated, err := client.Users.Update(ctx, "usr_01H...", &hellojohn.UpdateUserParams{
    DisplayName: hellojohn.String("Alice Smith"),
    Role:        hellojohn.String("admin"),
})

// Disable / delete
err = client.Users.Disable(ctx, "usr_01H...")
err = client.Users.Delete(ctx, "usr_01H...")

Session management

// List active sessions
sessions, err := client.Sessions.List(ctx, &hellojohn.SessionListParams{
    UserID: "usr_01H...",
})

// Revoke a session
err = client.Sessions.Revoke(ctx, "sess_01H...")

// Revoke all sessions for a user
err = client.Sessions.RevokeAll(ctx, &hellojohn.RevokeAllSessionsParams{
    UserID: "usr_01H...",
})

Tenant management

// Create a tenant
tenant, err := client.Tenants.Create(ctx, &hellojohn.CreateTenantParams{
    Name: "Acme Corp",
    Slug: "acme-corp",
})

// Get a tenant
tenant, err := client.Tenants.Get(ctx, "tnt_01H...")

// Update a tenant
updated, err := client.Tenants.Update(ctx, "tnt_01H...", &hellojohn.UpdateTenantParams{
    Name: hellojohn.String("Acme Corporation"),
})

// Delete
err = client.Tenants.Delete(ctx, "tnt_01H...")

Webhook verification

import "github.com/hellojohn/hellojohn-go/webhook"

http.HandleFunc("/webhooks/hellojohn", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    err := webhook.Verify(
        body,
        r.Header.Get("X-HelloJohn-Signature"),
        r.Header.Get("X-HelloJohn-Timestamp"),
        os.Getenv("WEBHOOK_SECRET"),
    )
    if err != nil {
        http.Error(w, "Invalid signature", http.StatusBadRequest)
        return
    }

    var event hellojohn.WebhookEvent
    json.Unmarshal(body, &event)

    switch event.Type {
    case "user.created":
        // handle user creation
    case "user.login":
        // handle login
    }

    w.WriteHeader(http.StatusOK)
})

Audit logs

logs, err := client.AuditLogs.List(ctx, &hellojohn.AuditLogListParams{
    Type:   "user.login_failed",
    Result: "failure",
    From:   time.Now().Add(-24 * time.Hour),
    Limit:  100,
})

for _, entry := range logs.Data {
    fmt.Printf("%s%s%s\n",
        entry.CreatedAt.Format(time.RFC3339),
        entry.Type,
        entry.Actor.Email,
    )
}

Error handling

import "github.com/hellojohn/hellojohn-go"

user, err := client.Users.Get(ctx, "usr_nonexistent")
if err != nil {
    var hjErr *hellojohn.Error
    if errors.As(err, &hjErr) {
        switch hjErr.Code {
        case "user_not_found":
            http.Error(w, "User not found", http.StatusNotFound)
        case "insufficient_permissions":
            http.Error(w, "Forbidden", http.StatusForbidden)
        default:
            http.Error(w, "Internal error", http.StatusInternalServerError)
        }
        return
    }
    // Network or other error
    log.Printf("unexpected error: %v", err)
}

Chi router example

Full example with Chi router and HelloJohn middleware:

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/hellojohn/hellojohn-go"
)

func main() {
    hj, _ := hellojohn.NewClient(hellojohn.Config{
        SecretKey: os.Getenv("HJ_SECRET_KEY"),
    })

    r := chi.NewRouter()

    // Public routes
    r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(`{"status":"ok"}`))
    })

    // Protected routes
    r.Group(func(r chi.Router) {
        r.Use(AuthMiddleware(hj))

        r.Get("/api/me", GetMe)
        r.Get("/api/data", GetData)
    })

    // Admin routes (require "admin" role)
    r.Group(func(r chi.Router) {
        r.Use(AuthMiddleware(hj))
        r.Use(RequireRole("admin"))

        r.Get("/api/admin/users", ListUsers)
    })

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

func RequireRole(role string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            payload := hellojohn.GetPayload(r.Context())
            if payload.Role != role {
                http.Error(w, "Forbidden", http.StatusForbidden)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

On this page