posthook

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 19 Imported by: 0

README

posthook-go

The official Go client library for the Posthook API.

Installation

go get github.com/posthook/posthook-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

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

func main() {
    client, err := posthook.NewClient("pk_...")
    if err != nil {
        log.Fatal(err)
    }

    hook, resp, err := client.Hooks.Schedule(context.Background(), &posthook.HookScheduleParams{
        Path:   "/webhooks/user-created",
        PostIn: "5m",
        Data:   map[string]any{"userId": "123", "event": "user.created"},
    })
    if err != nil {
        log.Printf("Failed to schedule hook: %v", err)
        return
    }

    fmt.Printf("Scheduled hook %s (status: %s)\n", hook.ID, hook.Status)
    if resp.Quota != nil {
        fmt.Printf("Quota: %d/%d remaining\n", resp.Quota.Remaining, resp.Quota.Limit)
    }
}

How It Works

Your Posthook project has a domain configured in the dashboard (e.g., webhook.example.com). When you schedule a hook, you specify a Path (e.g., /webhooks/user-created). At the scheduled time, Posthook delivers the hook by POSTing to the full URL (https://webhook.example.com/webhooks/user-created) with your data payload and signature headers.

Authentication

You can find your API key under Project Settings in the Posthook dashboard. Pass it directly to NewClient, or set the POSTHOOK_API_KEY environment variable:

// Explicit API key
client, err := posthook.NewClient("pk_...")

// From environment variable
client, err := posthook.NewClient("")  // reads POSTHOOK_API_KEY

For webhook signature verification, also provide a signing key:

client, err := posthook.NewClient("pk_...", posthook.WithSigningKey("ph_sk_..."))

Scheduling Hooks

Three scheduling modes are available:

Absolute UTC time (PostAt)

Schedule at an exact UTC time:

hook, _, err := client.Hooks.Schedule(ctx, &posthook.HookScheduleParams{
    Path:   "/webhooks/reminder",
    PostAt: time.Now().Add(24 * time.Hour),
    Data:   map[string]any{"userId": "123"},
})
Local time with timezone (PostAtLocal + Timezone)

Schedule at a local time that respects DST:

hook, _, err := client.Hooks.Schedule(ctx, &posthook.HookScheduleParams{
    Path:        "/webhooks/daily-digest",
    PostAtLocal: "2026-03-01T09:00:00",
    Timezone:    "America/New_York",
    Data:        map[string]any{"userId": "123"},
})
Relative delay (PostIn)

Schedule after a relative delay:

hook, _, err := client.Hooks.Schedule(ctx, &posthook.HookScheduleParams{
    Path:   "/webhooks/followup",
    PostIn: "30m",
    Data:   map[string]any{"userId": "123"},
})
Custom retry configuration

Override the default retry behavior for a specific hook:

hook, _, err := client.Hooks.Schedule(ctx, &posthook.HookScheduleParams{
    Path:   "/webhooks/critical",
    PostIn: "1m",
    Data:   map[string]any{"orderId": "456"},
    RetryOverride: &posthook.HookRetryOverride{
        MinRetries:    10,
        DelaySecs:     15,
        Strategy:      "exponential",
        BackoffFactor: 2.0,
        MaxDelaySecs:  3600,
        Jitter:        posthook.Bool(true),
    },
})

Managing Hooks

Get a hook
hook, _, err := client.Hooks.Get(ctx, "hook-uuid")
List hooks
hooks, _, err := client.Hooks.List(ctx, &posthook.HookListParams{
    Status:    posthook.StatusFailed,
    Limit:     50,
    SortBy:    posthook.SortByCreatedAt,
    SortOrder: posthook.SortOrderDesc,
})
fmt.Printf("Found %d hooks\n", len(hooks))
Cursor-based pagination

Use PostAtAfter as a cursor. After each page, advance it to the last hook's PostAt:

limit := 100
var cursor time.Time
for {
    hooks, _, err := client.Hooks.List(ctx, &posthook.HookListParams{
        Status:      posthook.StatusFailed,
        Limit:       limit,
        PostAtAfter: cursor,
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, hook := range hooks {
        fmt.Println(hook.ID, hook.FailureError)
    }

    if len(hooks) < limit {
        break // last page
    }
    cursor = hooks[len(hooks)-1].PostAt
}

Or use ListAll to auto-paginate:

iter := client.Hooks.ListAll(ctx, &posthook.HookListAllParams{
    Status: posthook.StatusFailed,
})
for hook, err := range iter {
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(hook.ID, hook.FailureError)
}
Delete a hook

Returns nil error on both 200 (deleted) and 404 (already delivered or gone):

_, err := client.Hooks.Delete(ctx, "hook-uuid")

Bulk Actions

Three bulk operations are available, each supporting by-IDs or by-filter:

  • Retry — Re-attempts delivery for failed hooks
  • Replay — Re-delivers completed hooks (useful for reprocessing)
  • Cancel — Cancels pending hooks before delivery
By IDs
result, _, err := client.Hooks.Bulk().Retry(ctx, &posthook.BulkActionByIDs{
    HookIDs: []string{"id-1", "id-2", "id-3"},
})
fmt.Printf("Retried %d hooks\n", result.Affected)
By filter
result, _, err := client.Hooks.Bulk().CancelByFilter(ctx, &posthook.BulkActionByFilter{
    StartTime:   time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC),
    EndTime:     time.Date(2026, 2, 22, 0, 0, 0, 0, time.UTC),
    EndpointKey: "/webhooks/deprecated",
    Limit:       500,
})
fmt.Printf("Cancelled %d hooks\n", result.Affected)

Verifying Webhook Signatures

When Posthook delivers a hook to your endpoint, it includes signature headers for verification. Use ParseDelivery to verify and parse the delivery:

client, _ := posthook.NewClient("pk_...", posthook.WithSigningKey("ph_sk_..."))

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    delivery, err := client.Signatures.ParseDelivery(body, r.Header)
    if err != nil {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return
    }

    fmt.Println(delivery.HookID)  // from Posthook-Id header
    fmt.Println(delivery.Path)    // "/webhooks/user-created"
    fmt.Println(delivery.PostAt)  // when it was scheduled
    fmt.Println(delivery.PostedAt) // when it was delivered

    // Unmarshal your custom data
    var event struct {
        UserID string `json:"userId"`
        Event  string `json:"event"`
    }
    json.Unmarshal(delivery.Data, &event)

    w.WriteHeader(http.StatusOK)
}
Custom tolerance

By default, signatures older than 5 minutes are rejected:

delivery, err := client.Signatures.ParseDelivery(body, r.Header,
    posthook.WithTolerance(10 * time.Minute),
)

Error Handling

All API errors are typed, enabling precise error handling with errors.As():

hook, _, err := client.Hooks.Get(ctx, "hook-id")
if err != nil {
    var authErr *posthook.AuthenticationError
    var notFound *posthook.NotFoundError
    var rateLimit *posthook.RateLimitError

    switch {
    case errors.As(err, &authErr):
        log.Fatal("Invalid API key")
    case errors.As(err, &notFound):
        log.Println("Hook not found")
    case errors.As(err, &rateLimit):
        log.Println("Rate limited, retry later")
    default:
        log.Printf("Unexpected error: %v", err)
    }
}

Available error types: BadRequestError (400), AuthenticationError (401), ForbiddenError (403), NotFoundError (404), PayloadTooLargeError (413), RateLimitError (429), InternalServerError (5xx), ConnectionError (network), SignatureVerificationError (signature).

Configuration

client, err := posthook.NewClient("pk_...",
    posthook.WithBaseURL("https://api.staging.posthook.io"),
    posthook.WithHTTPClient(&http.Client{Timeout: 60 * time.Second}),
    posthook.WithUserAgent("my-app/1.0"),
    posthook.WithSigningKey("ph_sk_..."),
)
Option Description Default
WithBaseURL Custom API base URL https://api.posthook.io
WithHTTPClient Custom *http.Client 30s timeout
WithUserAgent Custom User-Agent header posthook-go/1.0.0
WithSigningKey Signing key for signature verification

Quota Info

Every response includes quota information when available:

hook, resp, err := client.Hooks.Schedule(ctx, params)
if resp.Quota != nil {
    fmt.Printf("Limit: %d\n", resp.Quota.Limit)
    fmt.Printf("Usage: %d\n", resp.Quota.Usage)
    fmt.Printf("Remaining: %d\n", resp.Quota.Remaining)
    fmt.Printf("Resets at: %s\n", resp.Quota.ResetsAt.Format(time.RFC3339))
}

Documentation

Overview

Package posthook provides a Go client library for the Posthook API.

Posthook is a webhook scheduling and delivery platform. This SDK allows you to schedule, manage, and verify webhook deliveries from your Go applications.

Usage

client, err := posthook.NewClient("pk_...")
if err != nil {
    log.Fatal(err)
}

// Schedule a hook with a relative delay
hook, resp, err := client.Hooks.Schedule(ctx, &posthook.HookScheduleParams{
    Path:   "/webhooks/user-created",
    PostIn: "5m",
    Data:   map[string]any{"userId": "123"},
})

Authentication

Pass your API key directly to NewClient, or set the POSTHOOK_API_KEY environment variable and pass an empty string:

client, err := posthook.NewClient("")  // uses POSTHOOK_API_KEY

Verifying Webhook Signatures

When receiving deliveries, use the Signatures service to verify authenticity:

client, _ := posthook.NewClient("pk_...", posthook.WithSigningKey("ph_sk_..."))
delivery, err := client.Signatures.ParseDelivery(body, r.Header)

Index

Examples

Constants

View Source
const (
	StatusPending   = "pending"   // Hooks awaiting delivery (includes dispatching and retry).
	StatusRetry     = "retry"     // Hooks waiting to be retried after a failed attempt.
	StatusCompleted = "completed" // Hooks that were delivered successfully.
	StatusFailed    = "failed"    // Hooks that exhausted all retry attempts.
)

Hook status values for use with HookListParams.Status.

View Source
const (
	SortByPostAt    = "postAt"
	SortByCreatedAt = "createdAt"
)

Sort field values for use with HookListParams.SortBy.

View Source
const (
	SortOrderAsc  = "ASC"
	SortOrderDesc = "DESC"
)

Sort order values for use with HookListParams.SortOrder.

View Source
const (
	StrategyFixed       = "fixed"
	StrategyExponential = "exponential"
)

Retry strategy values for use with HookRetryOverride.Strategy.

View Source
const Version = "1.0.0"

Version is the semantic version of this SDK.

Variables

This section is empty.

Functions

func Bool

func Bool(v bool) *bool

Bool returns a pointer to the given bool value. This is a convenience helper for setting optional *bool fields like HookRetryOverride.Jitter.

Types

type AuthenticationError

type AuthenticationError struct{ Err *Error }

AuthenticationError is returned for HTTP 401 responses.

func (*AuthenticationError) Error

func (e *AuthenticationError) Error() string

func (*AuthenticationError) Unwrap

func (e *AuthenticationError) Unwrap() error

type BadRequestError

type BadRequestError struct{ Err *Error }

BadRequestError is returned for HTTP 400 responses.

func (*BadRequestError) Error

func (e *BadRequestError) Error() string

func (*BadRequestError) Unwrap

func (e *BadRequestError) Unwrap() error

type BulkActionByFilter

type BulkActionByFilter struct {
	StartTime   time.Time `json:"startTime"`
	EndTime     time.Time `json:"endTime"`
	EndpointKey string    `json:"endpointKey,omitempty"`
	SequenceID  string    `json:"sequenceID,omitempty"`
	Limit       int       `json:"limit,omitempty"`
}

BulkActionByFilter specifies hooks to act on using a time range filter.

type BulkActionByIDs

type BulkActionByIDs struct {
	HookIDs []string `json:"hookIDs"`
}

BulkActionByIDs specifies hooks to act on by their individual IDs.

type BulkActionResult

type BulkActionResult struct {
	Affected int `json:"affected"`
}

BulkActionResult contains the result of a bulk action.

type BulkActions

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

BulkActions provides methods for performing bulk operations on hooks.

func (*BulkActions) Cancel

Cancel cancels pending hooks specified by their IDs.

func (*BulkActions) CancelByFilter

func (b *BulkActions) CancelByFilter(ctx context.Context, params *BulkActionByFilter) (*BulkActionResult, *Response, error)

CancelByFilter cancels pending hooks matching a time range filter.

func (*BulkActions) Replay

Replay re-delivers completed hooks specified by their IDs.

func (*BulkActions) ReplayByFilter

func (b *BulkActions) ReplayByFilter(ctx context.Context, params *BulkActionByFilter) (*BulkActionResult, *Response, error)

ReplayByFilter re-delivers completed hooks matching a time range filter.

func (*BulkActions) Retry

Retry re-attempts delivery for failed hooks specified by their IDs.

func (*BulkActions) RetryByFilter

func (b *BulkActions) RetryByFilter(ctx context.Context, params *BulkActionByFilter) (*BulkActionResult, *Response, error)

RetryByFilter re-attempts delivery for failed hooks matching a time range filter.

type Client

type Client struct {

	// Hooks provides access to hook scheduling and management endpoints.
	Hooks *HooksService

	// Signatures provides webhook signature verification.
	Signatures *SignaturesService
	// contains filtered or unexported fields
}

Client manages communication with the Posthook API. A Client is safe for concurrent use by multiple goroutines.

func NewClient

func NewClient(apiKey string, opts ...Option) (*Client, error)

NewClient creates a new Posthook API client. If apiKey is empty, it falls back to the POSTHOOK_API_KEY environment variable. Returns an error if no API key is available.

Example
package main

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

func main() {
	client, err := posthook.NewClient("pk_...")
	if err != nil {
		panic(err)
	}

	_ = client // use client to schedule hooks, verify signatures, etc.
}

type ConnectionError

type ConnectionError struct{ Err *Error }

ConnectionError is returned for network or timeout errors.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type Delivery

type Delivery struct {
	HookID    string          `json:"hookId"`
	Timestamp int64           `json:"timestamp"`
	Path      string          `json:"path"`
	Data      json.RawMessage `json:"data"`
	PostAt    time.Time       `json:"postAt"`
	PostedAt  time.Time       `json:"postedAt"`
	CreatedAt time.Time       `json:"createdAt"`
	UpdatedAt time.Time       `json:"updatedAt"`

	// Body contains the raw HTTP request body bytes. It is not included
	// in JSON serialization and is provided for caller convenience.
	Body []byte `json:"-"`
}

Delivery is the parsed result of a verified webhook delivery. It contains the hook metadata extracted from headers and the parsed delivery body.

type Error

type Error struct {
	StatusCode int    `json:"statusCode"`
	Code       string `json:"code"`
	Message    string `json:"message"`
}

Error is the base error type returned by the Posthook API.

func (*Error) Error

func (e *Error) Error() string

type ForbiddenError

type ForbiddenError struct{ Err *Error }

ForbiddenError is returned for HTTP 403 responses.

func (*ForbiddenError) Error

func (e *ForbiddenError) Error() string

func (*ForbiddenError) Unwrap

func (e *ForbiddenError) Unwrap() error

type Hook

type Hook struct {
	ID                  string             `json:"id"`
	Path                string             `json:"path"`
	Domain              *string            `json:"domain,omitempty"`
	Data                json.RawMessage    `json:"data"`
	PostAt              time.Time          `json:"postAt"`
	Status              string             `json:"status"`
	PostDurationSeconds float64            `json:"postDurationSeconds"`
	Attempts            int                `json:"attempts,omitempty"`
	FailureError        string             `json:"failureError,omitempty"`
	SequenceData        *HookSequenceData  `json:"sequenceData,omitempty"`
	RetryOverride       *HookRetryOverride `json:"retryOverride,omitempty"`
	CreatedAt           time.Time          `json:"createdAt"`
	UpdatedAt           time.Time          `json:"updatedAt"`
}

Hook represents a scheduled webhook.

type HookListAllParams

type HookListAllParams struct {
	// Status filters hooks by status (e.g., StatusFailed).
	Status string

	// PostAtAfter is the start cursor: only return hooks scheduled after this time (exclusive).
	PostAtAfter time.Time

	// PageSize is the number of hooks to fetch per page (default 100, max 1000).
	PageSize int
}

HookListAllParams contains the parameters for auto-paginating hook listing. Uses cursor-based pagination via postAt ordering.

type HookListParams

type HookListParams struct {
	Status          string
	Limit           int
	Offset          int
	PostAtBefore    time.Time
	PostAtAfter     time.Time
	CreatedAtBefore time.Time
	CreatedAtAfter  time.Time
	SortBy          string
	SortOrder       string
}

HookListParams contains the query parameters for listing hooks. All fields are optional; zero values are ignored.

type HookRetryOverride

type HookRetryOverride struct {
	MinRetries    int     `json:"minRetries"`
	DelaySecs     int     `json:"delaySecs"`
	Strategy      string  `json:"strategy"`
	BackoffFactor float64 `json:"backoffFactor,omitempty"`
	MaxDelaySecs  int     `json:"maxDelaySecs,omitempty"`
	Jitter        *bool   `json:"jitter,omitempty"`
}

HookRetryOverride configures per-hook retry behavior.

type HookScheduleParams

type HookScheduleParams struct {
	// Path is the endpoint path that will be appended to your project's domain.
	Path string

	// Data is the JSON payload to deliver. Accepts any JSON-serializable value.
	Data any

	// PostAt schedules delivery at an absolute UTC time.
	PostAt time.Time

	// PostAtLocal schedules delivery at a local time. Must be used with Timezone.
	PostAtLocal string

	// Timezone is the IANA timezone for PostAtLocal (e.g., "America/New_York").
	Timezone string

	// PostIn schedules delivery after a relative delay (e.g., "5m", "2h").
	PostIn string

	// RetryOverride configures per-hook retry behavior.
	RetryOverride *HookRetryOverride
}

HookScheduleParams contains the parameters for scheduling a new hook. Exactly one of PostAt, PostAtLocal (with Timezone), or PostIn must be provided.

func (HookScheduleParams) MarshalJSON

func (p HookScheduleParams) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON marshaling that omits zero-value optional fields. This is needed because encoding/json's omitempty does not treat time.Time zero values as empty.

type HookSequenceData

type HookSequenceData struct {
	SequenceID        string `json:"sequenceID"`
	StepName          string `json:"stepName"`
	SequenceLastRunAt string `json:"sequenceLastRunAt"`
}

HookSequenceData contains sequence context for a hook that is part of a sequence.

type HooksService

type HooksService service

HooksService handles communication with the hook-related endpoints of the Posthook API.

func (*HooksService) Bulk

func (s *HooksService) Bulk() *BulkActions

Bulk returns a BulkActions handle for performing bulk operations on hooks.

func (*HooksService) Delete

func (s *HooksService) Delete(ctx context.Context, id string) (*Response, error)

Delete removes a scheduled hook. It returns nil error on both 200 (deleted) and 404 (already delivered or gone) — a hook you're canceling may have already been delivered, and that's not an error.

func (*HooksService) Get

func (s *HooksService) Get(ctx context.Context, id string) (*Hook, *Response, error)

Get retrieves a single hook by its ID.

func (*HooksService) List

func (s *HooksService) List(ctx context.Context, params *HookListParams) ([]*Hook, *Response, error)

List retrieves a paginated list of hooks. Use Limit and Offset on HookListParams for pagination. When the returned slice is shorter than the requested Limit, you have reached the last page.

func (*HooksService) ListAll

func (s *HooksService) ListAll(ctx context.Context, params *HookListAllParams) iter.Seq2[*Hook, error]

ListAll returns an iterator that yields every matching hook across all pages. It uses cursor-based pagination via postAt ordering for consistency.

Usage:

for hook, err := range client.Hooks.ListAll(ctx, &posthook.HookListAllParams{Status: posthook.StatusFailed}) {
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(hook.ID)
}

func (*HooksService) Schedule

func (s *HooksService) Schedule(ctx context.Context, params *HookScheduleParams) (*Hook, *Response, error)

Schedule creates a new scheduled hook. Exactly one of PostAt, PostAtLocal (with Timezone), or PostIn must be set on params.

Example
package main

import (
	"context"
	"fmt"

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

func main() {
	client, err := posthook.NewClient("pk_...")
	if err != nil {
		panic(err)
	}

	hook, _, err := client.Hooks.Schedule(context.Background(), &posthook.HookScheduleParams{
		Path:   "/webhooks/user-created",
		PostIn: "5m",
		Data:   map[string]any{"userId": "123"},
	})
	if err != nil {
		panic(err)
	}

	fmt.Println(hook.ID)
}
Example (PostAt)
package main

import (
	"context"
	"fmt"
	"time"

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

func main() {
	client, err := posthook.NewClient("pk_...")
	if err != nil {
		panic(err)
	}

	hook, _, err := client.Hooks.Schedule(context.Background(), &posthook.HookScheduleParams{
		Path:   "/webhooks/reminder",
		PostAt: time.Now().Add(24 * time.Hour),
		Data:   map[string]any{"userId": "123"},
	})
	if err != nil {
		panic(err)
	}

	fmt.Println(hook.ID)
}

type InternalServerError

type InternalServerError struct{ Err *Error }

InternalServerError is returned for HTTP 5xx responses.

func (*InternalServerError) Error

func (e *InternalServerError) Error() string

func (*InternalServerError) Unwrap

func (e *InternalServerError) Unwrap() error

type NotFoundError

type NotFoundError struct{ Err *Error }

NotFoundError is returned for HTTP 404 responses.

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

func (*NotFoundError) Unwrap

func (e *NotFoundError) Unwrap() error

type Option

type Option func(*Client) error

Option configures a Client.

func WithBaseURL

func WithBaseURL(rawURL string) Option

WithBaseURL sets a custom base URL for API requests.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets a custom HTTP client for API requests.

func WithSigningKey

func WithSigningKey(key string) Option

WithSigningKey sets the signing key used for webhook signature verification.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent sets a custom User-Agent header for API requests.

type PayloadTooLargeError

type PayloadTooLargeError struct{ Err *Error }

PayloadTooLargeError is returned for HTTP 413 responses.

func (*PayloadTooLargeError) Error

func (e *PayloadTooLargeError) Error() string

func (*PayloadTooLargeError) Unwrap

func (e *PayloadTooLargeError) Unwrap() error

type QuotaInfo

type QuotaInfo struct {
	Limit     int       `json:"limit"`
	Usage     int       `json:"usage"`
	Remaining int       `json:"remaining"`
	ResetsAt  time.Time `json:"resetsAt"`
}

QuotaInfo contains hook quota information parsed from response headers.

type RateLimitError

type RateLimitError struct{ Err *Error }

RateLimitError is returned for HTTP 429 responses.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

type Response

type Response struct {
	*http.Response
	Quota *QuotaInfo
}

Response wraps the standard http.Response and includes Posthook-specific metadata such as quota information. Note that the response body has already been read and closed; do not attempt to read from it.

type SignatureVerificationError

type SignatureVerificationError struct{ Err *Error }

SignatureVerificationError is returned when webhook signature verification fails.

func (*SignatureVerificationError) Error

func (*SignatureVerificationError) Unwrap

func (e *SignatureVerificationError) Unwrap() error

type SignaturesService

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

SignaturesService provides webhook signature verification.

func NewSignatures

func NewSignatures(signingKey string) (*SignaturesService, error)

NewSignatures creates a standalone SignaturesService for verifying webhook signatures without needing a full Client or API key. This is useful when you only need to receive and verify webhooks, not schedule hooks.

If signingKey is empty, it falls back to the POSTHOOK_SIGNING_KEY environment variable. An error is returned if no signing key is available from either source.

sigs, err := posthook.NewSignatures("whsec_your_signing_key")
delivery, err := sigs.ParseDelivery(body, req.Header)

func (*SignaturesService) ParseDelivery

func (s *SignaturesService) ParseDelivery(body []byte, headers http.Header, opts ...VerifyOption) (*Delivery, error)

ParseDelivery verifies the webhook signature from the provided headers and parses the delivery payload. It extracts the Posthook-Id, Posthook-Timestamp, and Posthook-Signature headers, validates the HMAC-SHA256 signature, and returns a parsed Delivery.

The body parameter should be the raw HTTP request body bytes.

Example
package main

import (
	"fmt"
	"io"
	"net/http"

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

func main() {
	client, err := posthook.NewClient("pk_...", posthook.WithSigningKey("ph_sk_..."))
	if err != nil {
		panic(err)
	}

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

		delivery, err := client.Signatures.ParseDelivery(body, r.Header)
		if err != nil {
			http.Error(w, "invalid signature", http.StatusUnauthorized)
			return
		}

		fmt.Println(delivery.HookID, delivery.Path)
		w.WriteHeader(http.StatusOK)
	})
}

type VerifyOption

type VerifyOption func(*verifyConfig)

VerifyOption configures signature verification behavior.

func WithTolerance

func WithTolerance(d time.Duration) VerifyOption

WithTolerance sets the maximum age of a webhook signature. Signatures older than this duration are rejected. The default is 5 minutes.

Jump to

Keyboard shortcuts

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