Backend
Go
net/http and encoding/json. Nothing to install.
package pulse
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
const base = "https://api.thepulseapi.com/api/v1"
type Signal struct {
Type string `json:"type"`
Value string `json:"value"`
}
type Client struct {
apiKey string
orgID string
http *http.Client
}
func New(apiKey, orgID string) *Client {
return &Client{
apiKey: apiKey,
orgID: orgID,
// http.DefaultClient has no timeout at all, which is a goroutine leak waiting
// for a slow upstream. Always supply one.
http: &http.Client{Timeout: 5 * time.Second},
}
}
func (c *Client) post(ctx context.Context, path string, body map[string]any, out any) error {
// organizationId is read from the body rather than from the key, and is required
// on nearly every endpoint.
body["organizationId"] = c.orgID
payload, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, base+path, bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", c.apiKey)
res, err := c.http.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode >= 300 {
return fmt.Errorf("pulse %s: %s", path, res.Status)
}
return json.NewDecoder(res.Body).Decode(out)
}
func (c *Client) Track(ctx context.Context, sessionID string, signals []Signal) (map[string]any, error) {
var out map[string]any
err := c.post(ctx, "/track", map[string]any{"sessionId": sessionID, "signals": signals}, &out)
return out, err
}
func (c *Client) RankedCatalog(ctx context.Context, sessionID string, limit int) (map[string]any, error) {
var out map[string]any
err := c.post(ctx, "/catalog/ranked", map[string]any{"sessionId": sessionID, "limit": limit}, &out)
return out, err
}p := pulse.New(os.Getenv("PULSE_API_KEY"), os.Getenv("PULSE_ORG_ID"))
ranked, err := p.RankedCatalog(r.Context(), sessionID, 24)
if err != nil {
// Serve the catalogue in its default order. Personalisation is an enhancement.
ranked = nil
}Pass the request context
Threading r.Context() through means a shopper who navigates away cancels the ranking call
instead of leaving it running against a response nobody will read.

