nahook

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 25, 2026 License: MIT Imports: 12 Imported by: 0

README

Nahook Go SDK

Official Go SDK for Nahook — the webhook delivery platform.

Installation

go get github.com/getnahook/nahook-go

Requires Go 1.21+.

Quick Start

Ingestion Client

Use the client package to send webhooks to your endpoints.

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    nahook "github.com/getnahook/nahook-go"
    "github.com/getnahook/nahook-go/client"
)

func main() {
    c, err := client.New("nhk_us_your_api_key",
        client.WithTimeout(10*time.Second),
        client.WithRetries(3),
    )
    if err != nil {
        log.Fatal(err)
    }

    // The SDK automatically routes requests to the correct regional API
    // based on your API key prefix (nhk_us_... → US, nhk_eu_... → EU,
    // nhk_ap_... → Asia Pacific). No configuration needed.
    //
    // To override for testing/local dev:
    //   c, err := client.New("nhk_us_...", client.WithBaseURL("http://localhost:3001"))
    //
    // For unit tests, mock the SDK client at the dependency injection boundary.
    // For integration tests, override the base URL to point at a local server.

    ctx := context.Background()

    // Send to a specific endpoint
    result, err := c.Send(ctx, "ep_abc123", nahook.SendOptions{
        Payload: map[string]interface{}{
            "event":   "order.created",
            "orderId": "ord_12345",
            "amount":  99.99,
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Delivery: %s (status: %s)\n", result.DeliveryID, result.Status)

    // Fan-out by event type
    triggerResult, err := c.Trigger(ctx, "order.paid", nahook.TriggerOptions{
        Payload: map[string]interface{}{
            "orderId": "ord_12345",
            "amount":  99.99,
        },
        Metadata: map[string]string{
            "region": "us-east-1",
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Triggered %d deliveries\n", len(triggerResult.DeliveryIDs))
}
Batch Operations
// Send to multiple endpoints at once (max 20)
batchResult, err := c.SendBatch(ctx, []nahook.SendBatchItem{
    {
        EndpointID: "ep_abc",
        Payload:    map[string]interface{}{"event": "user.created"},
    },
    {
        EndpointID: "ep_def",
        Payload:    map[string]interface{}{"event": "user.created"},
    },
})
if err != nil {
    log.Fatal(err)
}
for _, item := range batchResult.Items {
    if item.Error != nil {
        fmt.Printf("Item %d failed: %s\n", item.Index, item.Error.Message)
    } else {
        fmt.Printf("Item %d: delivery %s\n", item.Index, item.DeliveryID)
    }
}

// Fan-out multiple event types at once (max 20)
triggerBatchResult, err := c.TriggerBatch(ctx, []nahook.TriggerBatchItem{
    {
        EventType: "order.created",
        Payload:   map[string]interface{}{"orderId": "ord_1"},
    },
    {
        EventType: "invoice.paid",
        Payload:   map[string]interface{}{"invoiceId": "inv_1"},
    },
})
Management Client

Use the management package to administer workspaces, endpoints, event types, applications, subscriptions, and portal sessions.

package main

import (
    "context"
    "fmt"
    "log"

    nahook "github.com/getnahook/nahook-go"
    "github.com/getnahook/nahook-go/management"
)

func main() {
    mgmt, err := management.New("nhm_your_management_token")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()
    workspaceID := "ws_abc123"

    // ── Endpoints ──

    // List all endpoints
    endpoints, err := mgmt.Endpoints.List(ctx, workspaceID)
    if err != nil {
        log.Fatal(err)
    }
    for _, ep := range endpoints.Data {
        fmt.Printf("Endpoint: %s -> %s (active: %v)\n", ep.ID, ep.URL, ep.IsActive)
    }

    // Create an endpoint
    newEp, err := mgmt.Endpoints.Create(ctx, workspaceID, nahook.CreateEndpointOptions{
        URL:         "https://example.com/webhooks",
        Description: "Production webhook receiver",
        Metadata:    map[string]string{"env": "production"},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created endpoint: %s\n", newEp.ID)

    // Get an endpoint
    ep, err := mgmt.Endpoints.Get(ctx, workspaceID, newEp.ID)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Endpoint: %s\n", ep.URL)

    // Update an endpoint
    active := false
    updated, err := mgmt.Endpoints.Update(ctx, workspaceID, newEp.ID, nahook.UpdateEndpointOptions{
        IsActive: &active,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Updated endpoint active: %v\n", updated.IsActive)

    // Delete an endpoint
    err = mgmt.Endpoints.Delete(ctx, workspaceID, newEp.ID)
    if err != nil {
        log.Fatal(err)
    }

    // ── Event Types ──

    // List event types
    eventTypes, err := mgmt.EventTypes.List(ctx, workspaceID)
    if err != nil {
        log.Fatal(err)
    }
    for _, et := range eventTypes.Data {
        fmt.Printf("Event type: %s (%s)\n", et.Name, et.ID)
    }

    // Create an event type
    newET, err := mgmt.EventTypes.Create(ctx, workspaceID, nahook.CreateEventTypeOptions{
        Name:        "order.shipped",
        Description: "Fired when an order ships",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created event type: %s\n", newET.ID)

    // Get, update, delete follow the same pattern...

    // ── Applications ──

    // List with pagination
    limit := 10
    apps, err := mgmt.Applications.List(ctx, workspaceID, &nahook.ListOptions{
        Limit: &limit,
    })
    if err != nil {
        log.Fatal(err)
    }
    for _, app := range apps.Data {
        fmt.Printf("App: %s (%s)\n", app.Name, app.ID)
    }

    // Create an application
    newApp, err := mgmt.Applications.Create(ctx, workspaceID, nahook.CreateApplicationOptions{
        Name:       "Acme Corp",
        ExternalID: "customer_123",
        Metadata:   map[string]string{"plan": "enterprise"},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created app: %s\n", newApp.ID)

    // List endpoints for an application
    appEndpoints, err := mgmt.Applications.ListEndpoints(ctx, workspaceID, newApp.ID)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("App has %d endpoints\n", len(appEndpoints.Data))

    // Create endpoint under an application
    appEp, err := mgmt.Applications.CreateEndpoint(ctx, workspaceID, newApp.ID, nahook.CreateEndpointOptions{
        URL: "https://acme.com/webhooks",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created app endpoint: %s\n", appEp.ID)

    // ── Subscriptions ──

    // List subscriptions for an endpoint
    subs, err := mgmt.Subscriptions.List(ctx, workspaceID, appEp.ID)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Endpoint has %d subscriptions\n", len(subs.Data))

    // Create a subscription
    sub, err := mgmt.Subscriptions.Create(ctx, workspaceID, appEp.ID, nahook.CreateSubscriptionOptions{
        EventTypeID: newET.ID,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created subscription: %s\n", sub.ID)

    // Delete a subscription
    err = mgmt.Subscriptions.Delete(ctx, workspaceID, appEp.ID, newET.ID)
    if err != nil {
        log.Fatal(err)
    }

    // ── Portal Sessions ──

    // Create a portal session for an application
    session, err := mgmt.PortalSessions.Create(ctx, workspaceID, newApp.ID, &nahook.CreatePortalSessionOptions{
        Metadata: map[string]string{"userId": "user_456"},
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Portal URL: %s (expires: %s)\n", session.URL, session.ExpiresAt)

    // Create without options
    session2, err := mgmt.PortalSessions.Create(ctx, workspaceID, newApp.ID, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Portal URL: %s\n", session2.URL)

    // ── Environments ──

    // List environments
    envs, err := mgmt.Environments.List(ctx, workspaceID)
    if err != nil {
        log.Fatal(err)
    }
    for _, env := range envs.Data {
        fmt.Printf("Env: %s (%s, default: %v)\n", env.Name, env.Slug, env.IsDefault)
    }

    // Create an environment
    newEnv, err := mgmt.Environments.Create(ctx, workspaceID, nahook.CreateEnvironmentOptions{
        Name: "Staging",
        Slug: "staging",
    })

    // Update
    updatedName := "Pre-production"
    mgmt.Environments.Update(ctx, workspaceID, newEnv.ID, nahook.UpdateEnvironmentOptions{
        Name: &updatedName,
    })

    // Delete
    mgmt.Environments.Delete(ctx, workspaceID, newEnv.ID)

    // ── Event Type Visibility ──

    // List visibility for an environment
    vis, err := mgmt.Environments.ListEventTypeVisibility(ctx, workspaceID, newEnv.ID)

    // Set event type as published in an environment
    entry, err := mgmt.Environments.SetEventTypeVisibility(ctx, workspaceID, newEnv.ID, newET.ID, nahook.SetVisibilityOptions{
        Published: true,
    })
}
Error Handling
result, err := c.Send(ctx, "ep_123", nahook.SendOptions{
    Payload: map[string]interface{}{"test": true},
})
if err != nil {
    switch e := err.(type) {
    case *nahook.APIError:
        fmt.Printf("API error %d: %s (code: %s)\n", e.Status, e.Message, e.Code)

        if e.IsAuthError() {
            // 401 or 403 with token_disabled
            log.Fatal("Check your API key")
        }
        if e.IsNotFound() {
            // 404
            log.Fatal("Endpoint does not exist")
        }
        if e.IsRateLimited() {
            // 429 — RetryAfter may be set
            if e.RetryAfter != nil {
                fmt.Printf("Retry after %d seconds\n", *e.RetryAfter)
            }
        }
        if e.IsValidationError() {
            // 400
            fmt.Printf("Validation: %s\n", e.Message)
        }
        if e.IsRetryable() {
            // 5xx or 429 — consider retrying
        }

    case *nahook.NetworkError:
        fmt.Printf("Network error: %v\n", e.Cause)

    case *nahook.TimeoutError:
        fmt.Printf("Timed out after %dms\n", e.TimeoutMs)
    }
}
Custom Idempotency Key
// Provide your own idempotency key to deduplicate sends
result, err := c.Send(ctx, "ep_123", nahook.SendOptions{
    Payload:        map[string]interface{}{"orderId": "ord_123"},
    IdempotencyKey: "my-unique-key-abc",
})
// If not provided, a UUID v4 is generated automatically

Configuration

Client Options
Option Default Description
WithBaseURL(url) https://api.nahook.com API base URL
WithTimeout(d) 30s HTTP request timeout
WithRetries(n) 0 Max retries for retryable errors (5xx, 429, network)
Management Options
Option Default Description
WithBaseURL(url) https://api.nahook.com API base URL
WithTimeout(d) 30s HTTP request timeout

The management client does not support retries.

Retry Behavior

When retries are enabled (client only):

  • Retryable errors: 5xx, 429, network errors, timeouts
  • Non-retryable errors: 400, 401, 403, 404, 409, 413
  • Backoff: exponential with full jitter, base 500ms, max 10s
  • Formula: min(10s, 500ms * 2^attempt) * rand()
  • Respects Retry-After header when present

Auth

  • Client (ingestion): API keys starting with nhk_
  • Management: Management tokens starting with nhm_

Both will return an error at construction time if the prefix is invalid.

License

MIT

Documentation

Overview

Package nahook provides shared types, errors, and an internal HTTP client for the Nahook webhook platform SDKs.

Index

Constants

View Source
const (
	DefaultBaseURL = "https://api.nahook.com"
	DefaultTimeout = 30 * time.Second
	DefaultRetries = 0
)

Variables

This section is empty.

Functions

func PathEncode

func PathEncode(s string) string

PathEncode encodes a path segment for use in URLs. Internal: not part of the public API.

func ResolveBaseURL

func ResolveBaseURL(apiKey string) string

ResolveBaseURL extracts the region slug from an nhk_ API key and returns the regional base URL. Falls back to DefaultBaseURL for legacy keys.

Types

type APIError

type APIError struct {
	Status     int
	Code       string
	Message    string
	RetryAfter *int
}

APIError represents an error response from the Nahook API.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) IsAuthError

func (e *APIError) IsAuthError() bool

IsAuthError returns true for 401 or 403 with code "token_disabled".

func (*APIError) IsNotFound

func (e *APIError) IsNotFound() bool

IsNotFound returns true for 404 status codes.

func (*APIError) IsRateLimited

func (e *APIError) IsRateLimited() bool

IsRateLimited returns true for 429 status codes.

func (*APIError) IsRetryable

func (e *APIError) IsRetryable() bool

IsRetryable returns true for 5xx and 429 status codes.

func (*APIError) IsValidationError

func (e *APIError) IsValidationError() bool

IsValidationError returns true for 400 status codes.

type Application

type Application struct {
	ID         string            `json:"id"`
	ExternalID *string           `json:"externalId"`
	Name       string            `json:"name"`
	Metadata   map[string]string `json:"metadata"`
	CreatedAt  string            `json:"createdAt"`
	UpdatedAt  string            `json:"updatedAt"`
}

Application represents a developer application.

type BatchItemError

type BatchItemError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

BatchItemError is an error for a specific item in a batch.

type BatchResult

type BatchResult struct {
	Items []BatchResultItem `json:"items"`
}

BatchResult is the response from a batch operation.

type BatchResultItem

type BatchResultItem struct {
	Index          int             `json:"index"`
	DeliveryID     string          `json:"deliveryId,omitempty"`
	IdempotencyKey string          `json:"idempotencyKey,omitempty"`
	EventTypeID    string          `json:"eventTypeId,omitempty"`
	DeliveryIDs    []string        `json:"deliveryIds,omitempty"`
	Status         string          `json:"status,omitempty"`
	Error          *BatchItemError `json:"error,omitempty"`
}

BatchResultItem is the result for one item in a batch operation.

type CreateApplicationOptions

type CreateApplicationOptions struct {
	Name       string            `json:"name"`
	ExternalID string            `json:"externalId,omitempty"`
	Metadata   map[string]string `json:"metadata,omitempty"`
}

CreateApplicationOptions configures a new application.

type CreateEndpointOptions

type CreateEndpointOptions struct {
	URL          string                 `json:"url"`
	Type         string                 `json:"type,omitempty"`
	Description  string                 `json:"description,omitempty"`
	Metadata     map[string]string      `json:"metadata,omitempty"`
	Config       map[string]interface{} `json:"config,omitempty"`
	AuthUsername string                 `json:"authUsername,omitempty"`
	AuthPassword string                 `json:"authPassword,omitempty"`
	// EnvironmentID is optional. Public id (e.g. "env_abc123") of the environment
	// to scope this endpoint. If omitted, the workspace's default environment is used.
	EnvironmentID string `json:"environmentId,omitempty"`
}

CreateEndpointOptions configures a new endpoint.

type CreateEnvironmentOptions

type CreateEnvironmentOptions struct {
	Name string `json:"name"`
	Slug string `json:"slug"`
}

CreateEnvironmentOptions configures a new environment.

type CreateEventTypeOptions

type CreateEventTypeOptions struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

CreateEventTypeOptions configures a new event type.

type CreatePortalSessionOptions

type CreatePortalSessionOptions struct {
	Metadata         map[string]string `json:"metadata,omitempty"`
	Role             string            `json:"role,omitempty"`
	ExpiresInMinutes int               `json:"expiresInMinutes,omitempty"`
}

CreatePortalSessionOptions configures a new portal session.

type CreateSubscriptionOptions

type CreateSubscriptionOptions struct {
	EventTypeIDs []string `json:"eventTypeIds"`
}

CreateSubscriptionOptions configures a new subscription (bulk).

type Endpoint

type Endpoint struct {
	ID          string                 `json:"id"`
	URL         string                 `json:"url"`
	Description *string                `json:"description"`
	IsActive    bool                   `json:"isActive"`
	Type        string                 `json:"type"`
	Config      map[string]interface{} `json:"config"`
	Secret      string                 `json:"secret,omitempty"`
	Metadata    map[string]string      `json:"metadata,omitempty"`
	CreatedAt   string                 `json:"createdAt"`
	UpdatedAt   string                 `json:"updatedAt"`
}

Endpoint represents a webhook endpoint.

type Environment

type Environment struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Slug      string `json:"slug"`
	IsDefault bool   `json:"isDefault"`
	CreatedAt string `json:"createdAt"`
	UpdatedAt string `json:"updatedAt"`
}

Environment represents a workspace environment.

type EventType

type EventType struct {
	ID          string  `json:"id"`
	Name        string  `json:"name"`
	Description *string `json:"description"`
	CreatedAt   string  `json:"createdAt"`
}

EventType represents a registered event type.

type EventTypeVisibility

type EventTypeVisibility struct {
	EventTypeID   string `json:"eventTypeId"`
	EventTypeName string `json:"eventTypeName"`
	Published     bool   `json:"published"`
}

EventTypeVisibility represents the visibility of an event type in an environment.

type HTTPClient

type HTTPClient struct {
	// contains filtered or unexported fields
}

HTTPClient is the internal HTTP client shared by the client and management packages. Internal: not part of the public API.

func NewHTTPClient

func NewHTTPClient(cfg HTTPClientConfig) *HTTPClient

NewHTTPClient creates a new internal HTTP client. Internal: not part of the public API.

func (*HTTPClient) Request

func (c *HTTPClient) Request(ctx context.Context, opts RequestOptions, result interface{}) error

Request performs an HTTP request and decodes the JSON response into result. For DELETE (204) responses, result may be nil.

func (*HTTPClient) RequestWithStatus

func (c *HTTPClient) RequestWithStatus(ctx context.Context, opts RequestOptions, result interface{}) (int, error)

RequestWithStatus performs an HTTP request and returns the status code along with the decoded body.

type HTTPClientConfig

type HTTPClientConfig struct {
	Token   string
	BaseURL string
	Timeout time.Duration
	Retries int
}

HTTPClientConfig configures the internal HTTP client. Internal: not part of the public API.

type ListOptions

type ListOptions struct {
	Limit  *int
	Offset *int
}

ListOptions configures pagination for list operations.

type ListResult

type ListResult[T any] struct {
	Data []T `json:"data"`
}

ListResult wraps a list response.

type NetworkError

type NetworkError struct {
	Cause error
}

NetworkError represents a network-level failure where no HTTP response was received.

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type PortalSession

type PortalSession struct {
	URL       string `json:"url"`
	Code      string `json:"code"`
	ExpiresAt string `json:"expiresAt"`
}

PortalSession represents a portal session for developer self-service.

type RequestOptions

type RequestOptions struct {
	Method string
	Path   string
	Body   interface{}
	Query  map[string]string
}

RequestOptions describes an HTTP request to the Nahook API. Internal: not part of the public API.

type SendBatchItem

type SendBatchItem struct {
	EndpointID     string                 `json:"endpointId"`
	Payload        map[string]interface{} `json:"payload"`
	IdempotencyKey string                 `json:"idempotencyKey,omitempty"`
}

SendBatchItem represents one item in a batch send.

type SendOptions

type SendOptions struct {
	Payload        map[string]interface{} `json:"payload"`
	IdempotencyKey string                 `json:"idempotencyKey,omitempty"`
}

SendOptions configures a direct send to a specific endpoint.

type SendResult

type SendResult struct {
	DeliveryID     string `json:"deliveryId"`
	IdempotencyKey string `json:"idempotencyKey"`
	Status         string `json:"status"`
}

SendResult is the response from a direct send.

type SetVisibilityOptions

type SetVisibilityOptions struct {
	Published bool `json:"published"`
}

SetVisibilityOptions configures event type visibility in an environment.

type SubscribeResult

type SubscribeResult struct {
	Subscribed int `json:"subscribed"`
}

SubscribeResult is the response from subscribing an endpoint to event types.

type Subscription

type Subscription struct {
	ID            string `json:"id"`
	EventTypeID   string `json:"eventTypeId"`
	EventTypeName string `json:"eventTypeName"`
	CreatedAt     string `json:"createdAt"`
}

Subscription represents an event type subscription on an endpoint.

type TimeoutError

type TimeoutError struct {
	TimeoutMs int
}

TimeoutError represents a request that exceeded the configured timeout.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

type TriggerBatchItem

type TriggerBatchItem struct {
	EventType string                 `json:"eventType"`
	Payload   map[string]interface{} `json:"payload"`
	Metadata  map[string]string      `json:"metadata,omitempty"`
}

TriggerBatchItem represents one item in a batch trigger.

type TriggerOptions

type TriggerOptions struct {
	Payload  map[string]interface{} `json:"payload"`
	Metadata map[string]string      `json:"metadata,omitempty"`
}

TriggerOptions configures a fan-out trigger by event type.

type TriggerResult

type TriggerResult struct {
	EventTypeID string   `json:"eventTypeId"`
	DeliveryIDs []string `json:"deliveryIds"`
	Status      string   `json:"status"`
}

TriggerResult is the response from a trigger.

type UpdateApplicationOptions

type UpdateApplicationOptions struct {
	Name     *string           `json:"name,omitempty"`
	Metadata map[string]string `json:"metadata,omitempty"`
}

UpdateApplicationOptions configures an application update.

type UpdateEndpointOptions

type UpdateEndpointOptions struct {
	URL         *string           `json:"url,omitempty"`
	Description *string           `json:"description,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
	IsActive    *bool             `json:"isActive,omitempty"`
}

UpdateEndpointOptions configures an endpoint update.

type UpdateEnvironmentOptions

type UpdateEnvironmentOptions struct {
	Name *string `json:"name,omitempty"`
}

UpdateEnvironmentOptions configures an environment update.

type UpdateEventTypeOptions

type UpdateEventTypeOptions struct {
	Description *string `json:"description,omitempty"`
}

UpdateEventTypeOptions configures an event type update.

Directories

Path Synopsis
Package client provides the Nahook ingestion client for sending webhooks.
Package client provides the Nahook ingestion client for sending webhooks.
Package management provides the Nahook management API client for administering workspaces, endpoints, event types, applications, subscriptions, and portal sessions.
Package management provides the Nahook management API client for administering workspaces, endpoints, event types, applications, subscriptions, and portal sessions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL