silon

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 24 Imported by: 0

README

Silon Go SDK

Go Reference

Go client for the Silon messaging platform API — send messages on any channel (WhatsApp, SMS, email, push, web push, voice), manage CRM contacts and groups, run bulk campaigns, maintain the do-not-contact list, consume events, and verify webhooks. Stdlib only — zero third-party dependencies.

Installation

go get github.com/KUWAITNET/silon-go-sdk

Requires Go 1.24+. The module depends only on the Go standard library.

import "github.com/KUWAITNET/silon-go-sdk"

Quickstart

client, err := silon.NewClient(
    silon.WithAPIKey("sk_live_..."), // Settings → API keys; or set SILON_API_KEY
    silon.WithWorkspace("acme"),     // => https://acme.silon.tech; or SILON_WORKSPACE / SILON_BASE_URL
)
if err != nil {
    log.Fatal(err)
}

sent, err := client.Messages.Send(ctx, silon.MessageSendParams{
    Channel: "whatsapp",
    To:      map[string]any{"client_id": "cust_001"},
    Content: map[string]any{"body": "Your order has shipped"},
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(sent.ID, sent.Status) // e.g. "9f3e..." "queued"

There is one synchronous client; it is safe for concurrent use — run calls from goroutines and cancel them via the context.Context every method takes.

Sending

One endpoint, every channel. To targets a single recipient; Audience fans out a broadcast — exactly one of the two is required (a client-side *silon.Error is returned otherwise).

// Approved WhatsApp template to a raw number
client.Messages.Send(ctx, silon.MessageSendParams{
    Channel: "whatsapp",
    To:      map[string]any{"phone_number": "+12025550123"},
    WhatsAppTemplate: map[string]any{
        "name": "order_confirmed", "language": "en",
        "variables": map[string]any{"body_1": "Sara", "body_2": "ORD-42"},
    },
    Provider: silon.String("meta_cloud"),
})

Look up delivery status with Messages.Retrieve. The modern shape carries ID, Object, Channel (nullable) and a typed Timeline — the ordered status transitions ({Status, At, Provider}, ascending by At). A single send's Timeline runs queued → sent → delivered when the channel reports receipts; delivered appears only in the timeline, never as the top-level Status:

status, err := client.Messages.Retrieve(ctx, sent.ID)
for _, step := range status.Timeline {
    fmt.Println(step.Status, step.At) // queued, sent, delivered, ...
}

The legacy EventID, IsSent and per-recipient Messages fields are still populated but deprecated (gopls / staticcheck flag them) — prefer ID, Status and Timeline.

Batches

Many independent, personalised messages in one call — use it when every recipient gets different content (for one content fanned out to an audience, use a broadcast). Exactly one of Messages (up to 500 inline rows) or File (a saved CSV name) is required — a client-side *silon.Error is returned otherwise. Request-level fields (Channel, Content, Template, Provider, ...) are row defaults on both forms; a row's own field (or CSV column) always wins.

Inline rows are free-form maps with the same shape as a single send minus audience:

batch, err := client.Messages.SendBatch(ctx, silon.MessageBatchParams{
    Channel: silon.String("sms"),
    Messages: []map[string]any{
        {"to": map[string]any{"phone_number": "+96550001234"},
            "content": map[string]any{"body": "Sara, your table for 2 is confirmed."}},
        {"to": map[string]any{"phone_number": "+96550001235"},
            "content": map[string]any{"body": "Omar, your table for 4 is confirmed."}},
    },
})
for _, row := range batch.Messages { // request order
    fmt.Println(row.ID, row.Status)  // each ID works with Messages.Retrieve
}

Validation is all-or-nothing: every row is validated up front and any invalid row 422s the whole batch with a per-index Attr like messages[3].to.phone_number — nothing is queued. An empty list is a 422 batch-empty; more than 500 rows is a 422 batch-too-large. Inline batches have no GET endpoint — the per-row message ids are the tracking primitive. Requires the messages:send scope.

For unbounded row counts, upload a CSV once and send it by name — rows expand asynchronously, so the 202 is the aggregate envelope only (Messages is nil):

upload, err := client.Bulk.Files.Upload(ctx, silon.BulkFileUploadParams{
    Path: "recipients.csv", // e.g. name,phone_number columns
})
batch, err := client.Messages.SendBatch(ctx, silon.MessageBatchParams{
    File:    silon.String(upload.Name),
    Channel: silon.String("sms"),
    Content: map[string]any{"body": "Hello {{name}}"}, // {{columns}} render per row
})
fmt.Println(batch.ID, batch.Status) // "queued"; RowCount when cheaply known

The file-form batch.ID is the created bulk batch id — read per-row status with client.Bulk.Retrieve(ctx, id) and the bulk reports. A heterogeneous CSV (per-row channel / message columns) needs no defaults at all. An unknown File name is a 404 file-not-found; defaults the bulk pipeline cannot honor are rejected with a 422 batch-invalid. This one endpoint replaces the deprecated Bulk.Send for every file shape.

Broadcasts

One piece of content fanned out to an audience — a CRM group, explicit client ids, or an inline ad-hoc list of raw addresses (max 1,000 rows; duplicates are deduped, suppressed recipients skipped — SkippedCount is the total and Skipped itemises it per reason):

// Email broadcast to a client group
result, err := client.Broadcasts.Create(ctx, silon.BroadcastCreateParams{
    Channel:  "email",
    Audience: map[string]any{"type": "client_group", "slug": "vip"},
    Content:  map[string]any{"subject": "We saved you a seat", "body": "<h1>Hello</h1>"},
})
fmt.Println(result.TargetCount, result.SkippedCount)
if result.Skipped != nil { // additive breakdown; SkippedCount stays the sum
    fmt.Println(result.Skipped.Suppressed, result.Skipped.WrongChannel, result.Skipped.Duplicate)
}

// SMS to an ad-hoc recipient list
client.Broadcasts.Create(ctx, silon.BroadcastCreateParams{
    Channel: "sms",
    Audience: map[string]any{
        "type": "recipients",
        "recipients": []any{
            map[string]any{"phone_number": "+96550001234"},
            map[string]any{"phone_number": "+96550001235"},
            map[string]any{"client_id": "cust_001"},
        },
    },
    Content: map[string]any{"body": "Flash sale ends tonight"},
})

// Track it
broadcast, err := client.Broadcasts.Retrieve(ctx, result.ID)
page, err := client.Broadcasts.Deliveries(ctx, result.ID, silon.BroadcastDeliveriesParams{})
for delivery, err := range page.All(ctx) {
    if err != nil {
        return err
    }
    fmt.Println(delivery.ClientID, delivery.Status)
}

Requires the broadcasts:send scope. (Messages.Send with an Audience keeps working as a legacy alias for the same fan-out.)

Every Messages.Send / Messages.SendBatch / Broadcasts.Create / OTP.Send call carries an Idempotency-Key header (auto-generated UUIDv4 unless you set IdempotencyKey), so automatic retries can never double-send.

Optional scalar params are pointers — use the helpers silon.String, silon.Int, silon.Bool, silon.Float, silon.Time. Fields this SDK version does not model can be passed via ExtraBody, merged into the request body last.

Scheduling and cancellation

Messages.Send, Broadcasts.Create, and the file form of Messages.SendBatch take an optional SendAt (*time.Time, serialized ISO-8601 with the value's own UTC offset). The server requires it to be strictly in the future and at most 90 days ahead — otherwise a 422 send-at-invalid. A scheduled create answers the normal 202 envelope with status "scheduled":

sent, err := client.Messages.Send(ctx, silon.MessageSendParams{
    Channel: "sms",
    To:      map[string]any{"phone_number": "+96550001234"},
    Content: map[string]any{"body": "Doors open in one hour."},
    SendAt:  silon.Time(time.Now().Add(24 * time.Hour)),
})
fmt.Println(sent.Status) // "scheduled"

The envelope ID is stable across the lifecycle — it resolves via Messages.Retrieve / Broadcasts.Retrieve before and after dispatch (scheduled, then the normal queued/sent lifecycle; the status endpoints also report SendAt). While a send is still scheduled, cancel it:

canceled, err := client.Messages.Cancel(ctx, sent.ID)
fmt.Println(canceled.Status) // "canceled"

// Same shape for broadcasts:
canceled2, err := client.Broadcasts.Cancel(ctx, broadcast.ID)

A canceled send never dispatches and emits a message.canceled / broadcast.canceled event. Cancel is idempotent by nature and sends no Idempotency-Key: canceling an already-canceled send answers the same 200 envelope again (no second event). Once dispatched (or for an immediate send's id) the server answers a 409 not-cancellable; an unknown id is a 404.

Notes:

  • SendAt with inline batch rows is rejected with a 422 batch-invalid (no batch cancel resource exists by design) — schedule rows individually via Messages.Send, or use the file form (rows expand and send at dispatch time; the file-form envelope answers "scheduled").
  • Scheduled creates stay always-keyed, exactly like immediate ones — an idempotent replay returns the scheduled envelope.
  • On a scheduled broadcast envelope, TargetCount / SkippedCount may be null (decoded as 0) until the audience resolves at dispatch time.
  • Statuses: scheduled and canceled join the documented sets — message scheduled|queued|sent|failed|canceled, broadcast scheduled|in_progress|completed|failed|canceled.
  • Test-mode (sk_test_) scheduled sends simulate on dispatch, like any other test-mode traffic.

Suppressions

A per-workspace do-not-contact list, enforced on every send path. A row matches on (address, channel) or — with no channel — on the address across all channels; addresses are stored normalized (compact E.164 / lowercase email), so any formatting of the same address matches.

// Suppress an address on one channel (omit Channel for all channels)
sup, err := client.Suppressions.Create(ctx, silon.SuppressionCreateParams{
    Address: "+96550001234",
    Channel: silon.String("sms"),
    Reason:  silon.String(silon.SuppressionReasonStop), // default "manual"
})

// List (cursor-paginated) with optional filters
page, err := client.Suppressions.List(ctx, silon.SuppressionListParams{
    Reason: silon.String(silon.SuppressionReasonUnsubscribe),
})
for sup, err := range page.All(ctx) {
    if err != nil {
        return err
    }
    fmt.Println(sup.Address, sup.Reason) // sup.Channel == nil => all channels
}

// Make the address contactable again
err = client.Suppressions.Delete(ctx, sup.ID)

Create is idempotent by nature: creating a duplicate (Address, Channel) in the same mode answers 200 with the existing suppression — never an error — so it sends no Idempotency-Key. Reasons: manual, unsubscribe, hard_bounce, stop (the silon.SuppressionReason* constants). List requires the suppressions:read scope; Create / Delete require suppressions:write.

Enforcement:

  • Single-recipient sends (Messages.Send with To, OTP.Send) to a suppressed address are rejected with a 422 recipient-suppressed.
  • Fan-outs (broadcasts, batch inline rows, batch file/CSV rows, legacy bulk) skip suppressed recipients instead — never an error. The broadcast/batch envelopes itemise the skips in the additive Skipped breakdown (Suppressed / WrongChannel / Duplicate), with SkippedCount staying the sum. Suppressed inline-batch rows are omitted from the per-row Messages; the file form reports its breakdown on the bulk read side (Bulk.Retrieve) once async expansion runs. Skipped is nil on servers predating the breakdown and on scheduled broadcast envelopes whose audience resolves at dispatch time.
  • Suppressions are mode-scoped: test keys list/manage/enforce test suppressions only, live keys live ones.

Transactional/legal sends (e.g. a receipt owed to an unsubscribed customer) can bypass a suppression per request — single-recipient sends only:

sent, err := client.Messages.Send(ctx, silon.MessageSendParams{
    Channel:             "email",
    To:                  map[string]any{"email": "sara@example.com"},
    Content:             map[string]any{"subject": "Your receipt", "body": "..."},
    OverrideSuppression: silon.Bool(true),
})

OverrideSuppression requires the suppressions:override scope, which is in no scope preset and must be granted explicitly — without it the request is a 403 missing-scope; alongside Audience (or on batches / broadcasts) it is a 422 override-not-allowed. An overridden send proceeds and its delivery row is flagged suppression_overridden: true.

Templates

Slug-keyed message templates with an immutable version spine — the same rows a send renders for Template: {"slug": ...}. Any change to a content field (Subject / Body / BodyMd) mints an immutable version N+1; Channel is metadata and never bumps the version.

tmpl, err := client.Templates.Create(ctx, silon.TemplateCreateParams{
    Slug:    "order-shipped",
    Channel: silon.String("email"),
    Subject: silon.String("Your order shipped"),
    BodyMd:  silon.String("Hi {{ name }}, it's on the way."),
})
// tmpl.Version == 1, tmpl.Versions == []int{1}

updated, err := client.Templates.Update(ctx, "order-shipped", silon.TemplateUpdateParams{
    Subject: silon.String("Shipped today"),
}) // mints version 2

page, err := client.Templates.List(ctx, silon.TemplateListParams{Q: silon.String("order")})
err = client.Templates.Delete(ctx, "order-shipped") // soft archive

Pin an older revision on any send path (Messages.Send, Broadcasts.Create, Messages.SendBatch row/defaults) with an optional integer version; omit it to render the latest:

sent, err := client.Messages.Send(ctx, silon.MessageSendParams{
    Channel:  "email",
    To:       map[string]any{"client_id": "cust_001"},
    Template: map[string]any{"slug": "order-shipped", "version": 1},
})

An unknown pinned version is a 422 template-version-not-found. List / Retrieve require the templates:read scope; Create / Update / Delete require templates:write. Delete is a soft archive: the slug stays reserved (re-create is a 409 template-exists) and archived slugs read as template-not-found everywhere.

Webhook testing and delivery attempts

WebhookEndpoints.Test synchronously POSTs a signed ping to the endpoint and returns the outcome — a failing sink is not an error (the result carries Delivered: false and the reason in Error):

result, err := client.WebhookEndpoints.Test(ctx, endpoint.ID)
if err != nil { /* auth / unknown id only */ }
fmt.Println(result.Delivered, result.ResponseStatus, result.LatencyMs, result.Error)

WebhookEndpoints.ListAttempts pages through the (event, endpoint) delivery ledger (newest first) — each row is a webhook_attempt with Attempts, ResponseStatus (nil when the endpoint never answered), OK, Error, and the attempt timestamps. Test pings are never persisted and never appear here.

Test mode

Create an sk_test_ API key (Settings → API keys) to integrate and CI-test without a provider account or a real message. Test-mode requests run the full pipeline — validation, scopes, rate limits, idempotency, delivery rows, events — but never reach a provider and never bill. Affected response models (MessageAccepted, MessageStatus, BroadcastAccepted, Broadcast, BatchAccepted, OTPSendResult, OTPVerifyResult, Event, WebhookEndpoint) carry a Livemode field: false for test traffic, true for live.

Magic recipients force deterministic outcomes in test mode. Statuses settle asynchronously a few seconds after the 202, so polling and webhooks behave realistically:

Recipient Outcome
+15005550001 delivered
+15005550002 failed (simulated provider error)
+15005550009 always suppressed (no suppression row needed)
delivered@silon.test delivered
bounce@silon.test failed
suppressed@silon.test always suppressed (no suppression row needed)
anything else delivered

The always-suppressed recipients behave exactly like a real suppression: a single send is rejected with a 422 recipient-suppressed, and a fan-out skips them into the envelope's Skipped.Suppressed counter.

With a live key, magic recipients are rejected with a 422 test-recipient-in-live — test fixtures can never leak into real sends.

Test-mode OTPs are never dispatched; the magic code 000000 — and only it — verifies:

sent, err := client.OTP.Send(ctx, silon.OTPSendParams{
    Purpose: "login",
    To:      map[string]any{"phone_number": "+15005550001"},
})
result, err := client.OTP.Verify(ctx, silon.OTPVerifyParams{
    OTPID: sent.OTPID,
    Code:  "000000",
})
fmt.Println(result.Verified, result.Livemode) // true false

Webhook endpoints are mode-routed at create time: live endpoints receive events from live sends only, and Livemode: silon.Bool(false) endpoints receive test-mode events only — register one endpoint per mode to consume both streams:

endpoint, err := client.WebhookEndpoints.Create(ctx, silon.WebhookEndpointCreateParams{
    URL:      "https://ci.example.com/hooks/silon-test",
    Livemode: silon.Bool(false), // default is true (live events only)
})

Resources

Resource Methods
client.Messages Send, SendBatch, Retrieve, Cancel
client.Broadcasts Create, Retrieve, Deliveries (paginated), Cancel
client.OTP Send, Verify
client.Clients ListPage (paginated), List (deprecated), Create, Retrieve, Update, Replace, Delete
client.ClientGroups ListPage (paginated), List (deprecated), Create, Retrieve, Update, Replace, Delete
client.Bulk List, Retrieve, Send (deprecated), Files.List, Files.Upload, Recipients.Retrieve
client.Reports Messages, Channels, Clients, Users, Bulks, SpecificBulks, Subscriptions, AWSUsage, Balance
client.WhatsAppTemplates List, Retrieve
client.Templates List (paginated), Create, Retrieve, Update, Delete
client.WebhookEndpoints List (paginated), Create, Retrieve, Update, Delete, Test, ListAttempts (paginated)
client.Suppressions List (paginated), Create, Delete
client.Events List (paginated), Retrieve
client.Push SubscribeAndroid, SubscribeIOS, UpsertDevices, MarkRead, ListNotifications, SubscribeWeb
client.Profile Retrieve, Update, Replace
client.Auth Signup, Login (deprecated)

Deprecated operations (Bulk.Send, Push.ListNotifications, Push.SubscribeWeb, Auth.Login, Clients.List, ClientGroups.List) carry Go's standard // Deprecated: doc-comment marker, which gopls and staticcheck surface at the call site. Bulk.Send's successor for every shape is Messages.SendBatch (inline rows or a saved CSV via File); Bulk.Files.Upload / Files.List stay current as the CSV ingestion path.

CRM list grammar (C2). The canonical CRM contacts and groups routes are now plural and cursor-paginated (/api/v1/crm/clients/, /api/v1/crm/groups/). Clients.ListPage / ClientGroups.ListPage are the new paginated methods and return a *silon.Page[T]. The pre-C2 Clients.List / ClientGroups.List still work unchanged — they return a bare []T off the frozen singular routes and are safe for existing for _, x := range / len / index call sites — but are now marked deprecated in favour of ListPage. All CRM CRUD (Create, Retrieve, Update, Replace, Delete) targets the canonical plural routes.

Pagination

Cursor-paginated lists (Events.List, WebhookEndpoints.List, WebhookEndpoints.ListAttempts, Templates.List, Suppressions.List, Broadcasts.Deliveries, Clients.ListPage, ClientGroups.ListPage) return a *silon.Page[T] you can walk manually or drain with the lazy range-over-func iterator All:

page, err := client.Events.List(ctx, silon.EventListParams{
    Type:  silon.String("message.failed"),
    Limit: silon.Int(100),
})

for _, event := range page.Results { // this page only
    ...
}

for event, err := range page.All(ctx) { // every page, lazily
    if err != nil {
        return err
    }
    ...
}

// or manually
for page.HasNextPage() {
    page, err = page.NextPage(ctx)
    ...
}

NextPage on the last page (HasNextPage() == false) returns an error — check HasNextPage first. All fetches each next page only when the iteration reaches it and yields a single non-nil error (then stops) if a page fetch fails.

Errors

Non-2xx responses return a *silon.APIError carrying the parsed error payload. Inspect it with errors.As or the status predicates:

_, err := client.Messages.Send(ctx, silon.MessageSendParams{
    Channel: "banana",
    To:      map[string]any{"client_id": "x"},
})

var apiErr *silon.APIError
if errors.As(err, &apiErr) {
    fmt.Println(apiErr.StatusCode, apiErr.RequestID)
    for _, detail := range apiErr.Errors {
        fmt.Println(detail.Code, detail.Attr, detail.Detail)
    }
}

if silon.IsRateLimit(err) { // 429
    fmt.Println("retry after", *apiErr.RetryAfter, "seconds")
}

Predicates: IsBadRequest (400), IsAuthentication (401), IsPermissionDenied (403), IsNotFound (404), IsConflict (409, idempotency-key reuse), IsGone (410, expired OTP), IsUnprocessableEntity (422), IsRateLimit (429, with RetryAfter parsed from Retry-After / RateLimit-Reset), IsInternalServer (5xx).

APIError.Retryable (*bool) mirrors the error body's retryable flag: true iff retrying the same request could ever succeed (429, 5xx, or an in-flight idempotency twin), false for every other 4xx. It is read verbatim from the body — never recomputed from the status code — and is nil when a legacy / non-v1 body omits the field.

Transport failures (the request never produced an HTTP response) are *silon.ConnectionError — its Timeout field is true for timeouts and Unwrap() exposes the underlying error. Configuration, client-side validation, and response-parse failures are the base *silon.Error.

Retries

Requests are retried automatically (default WithMaxRetries(2), exponential backoff with jitter, honouring Retry-After / RateLimit-Reset, delays clamped to 30s) — but only when it is safe: idempotent methods (GET/HEAD/OPTIONS/PUT/DELETE) plus POSTs that carry an Idempotency-Key, and only on connection errors or HTTP 429/500/502/503/504. Other POST/PATCH requests are never retried. The same Idempotency-Key value is replayed on every attempt, and retry sleeps respect context.Context cancellation.

Webhooks

Verify the Silon-Signature header on deliveries with the endpoint's one-time whsec_ secret — no HTTP client needed:

func handler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body) // raw bytes, not parsed JSON

    event, err := silon.ConstructWebhookEvent(
        body, r.Header.Get(silon.SignatureHeader),
        os.Getenv("SILON_WEBHOOK_SECRET"), silon.DefaultWebhookTolerance,
    )
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    if event.Type == "broadcast.completed" {
        fmt.Println(*event.Data.Sent, "delivered,", *event.Data.Failed, "failed")
    }
}

VerifyWebhookSignature(payload, header, secret, tolerance) returns a bool (constant-time compare; tolerance <= 0 skips the freshness check; malformed headers are false, never an error). SignWebhookPayload produces valid headers for tests and mocks.

Configuration

Option Env var Default
WithAPIKey SILON_API_KEY — (required)
WithWorkspace SILON_WORKSPACE
WithBaseURL SILON_BASE_URL https://<workspace>.silon.tech
WithTimeout 30 s
WithMaxRetries 2
WithDefaultHeader
WithHTTPClient internally built *http.Client

A base URL must be resolvable at construction time, from one of four sources checked in this order — otherwise NewClient returns a *silon.Error immediately (errors are returned, never panicked):

  1. WithBaseURL(...) (wins over everything)
  2. SILON_BASE_URL env var
  3. WithWorkspace(...)https://<workspace>.silon.tech
  4. SILON_WORKSPACE env var → same expansion

A trailing slash on the base URL is stripped. WithDefaultHeader(k, v) adds a header to every request (repeatable; later values for the same key win), and WithHTTPClient supplies your own *http.Client for full transport control.

On-prem / self-hosted instances

The WithWorkspace shortcut is SaaS-only sugar; everything else in the SDK is host-agnostic. For a self-hosted Silon, point WithBaseURL at your instance:

client, err := silon.NewClient(
    silon.WithAPIKey("sk_live_..."),
    silon.WithBaseURL("https://silon.customer.internal"),
)

API keys, the error contract, retries, idempotency, and webhook signature verification all behave identically — they ride on the base URL.

Private CA / self-signed TLS. Supply your own *http.Client with a custom root pool:

caPEM, err := os.ReadFile("/etc/pki/customer-ca.pem")
if err != nil {
    log.Fatal(err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caPEM)

httpClient := &http.Client{
    Timeout: 30 * time.Second, // your client's Timeout governs
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{RootCAs: pool},
    },
}

client, err := silon.NewClient(
    silon.WithAPIKey("sk_live_..."),
    silon.WithBaseURL("https://10.20.0.5"),
    silon.WithHTTPClient(httpClient),
)

Note that a custom HTTP client brings its own timeout — set Timeout on the *http.Client, since WithTimeout only applies to the transport the SDK constructs itself.

Reverse proxies. Cursor pagination never follows the server's opaque next URL directly — the SDK extracts only its query parameters and re-issues the request against your configured base URL, so a proxy that rewrites hostnames can't send pagination to an unreachable internal host.

Platform adaptations (approved deviations from SPEC)

  • Single error type + predicates instead of the per-status subclass hierarchy: *APIError with Is* helpers (Go has no exception subclassing).
  • Errors are returned, never panicked/thrown; fail-fast construction means NewClient returns an error. Client-side validation failures (e.g. the To/Audience XOR on Messages.Send) return the base *Error where Python raises ValueError.
  • Unknown response fields are ignored (Go encoding/json default) rather than preserved as dynamic attributes like Python's extra="allow".
  • One synchronous client — no separate async variant; use goroutines and context.Context.
  • WithHTTPClient disables WithTimeout; the supplied client's own Timeout governs.
  • Bulk.Files.Upload replaces Python's dynamically-typed file argument with BulkFileUploadParams — exactly one of Path / Content / Reader (a *silon.Error otherwise), with the multipart filename resolved as Filename override → Path base name → Name() of the reader (e.g. *os.File) → recipients.csv.
  • BulkSendParams.Files matches the wire field name for message attachments, where Python names the keyword attachments.
  • Deprecated operations (Push.ListNotifications, Push.SubscribeWeb, Auth.Login) carry Go's standard // Deprecated: doc-comment marker (surfaced by godoc/gopls/staticcheck) instead of Python's runtime DeprecationWarning.
  • Push.ListNotifications takes a typed PushPlatform (PushPlatformAndroid / PushPlatformIOS / PushPlatformCombined, the zero value) instead of Python's "android"|"ios"|None; an unknown platform returns the base *Error client-side without a request.
  • PushNotification.Date stays a string — the legacy feed returns date-only values, not ISO-8601 date-times.
  • Auth.Signup returns *UserProfile (the created profile) rather than a distinct SignupResult subclass as in Python.

Development

cd sdk/go
go test ./... -count=1
go vet ./...

The test suite is fully offline (net/http/httptest); no real network calls.

Documentation

Overview

Package silon is the official Go SDK for the Silon API.

Construct a client with functional options; configuration falls back to the SILON_API_KEY, SILON_BASE_URL and SILON_WORKSPACE environment variables:

client, err := silon.NewClient(
	silon.WithAPIKey("sk_live_..."),
	silon.WithWorkspace("acme"), // => https://acme.silon.tech
)
if err != nil {
	log.Fatal(err)
}

sent, err := client.Messages.Send(ctx, silon.MessageSendParams{
	Channel: "whatsapp",
	To:      map[string]any{"client_id": "cust_001"},
	Content: map[string]any{"body": "Your order has shipped"},
})

Every operation takes a context.Context as its first argument. API failures are returned as *APIError (inspect with errors.As or the Is* predicate helpers such as IsNotFound); transport failures as *ConnectionError; configuration, validation and parse failures as the base *Error.

The SDK retries idempotent requests (GET/HEAD/OPTIONS/PUT/DELETE, plus any request carrying an Idempotency-Key header) on connection errors and HTTP 429/500/502/503/504, with exponential backoff that honours the server's Retry-After / RateLimit-Reset hints. Messages.Send and OTP.Send always send an Idempotency-Key (auto-generated UUIDv4 when not supplied), so their retries can never double-send.

Webhook deliveries are verified without an HTTP client via VerifyWebhookSignature and ConstructWebhookEvent.

Index

Constants

View Source
const (
	SuppressionReasonManual      = "manual"
	SuppressionReasonUnsubscribe = "unsubscribe"
	SuppressionReasonHardBounce  = "hard_bounce"
	SuppressionReasonStop        = "stop"
)

Suppression reasons accepted by SuppressionCreateParams.Reason and the SuppressionListParams.Reason filter.

View Source
const DefaultMaxRetries = 2

DefaultMaxRetries is how many times a failed retryable request is retried when WithMaxRetries is not used.

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout is the request timeout applied to the internally created HTTP client when WithTimeout / WithHTTPClient are not used.

View Source
const DefaultWebhookTolerance = 300

DefaultWebhookTolerance is the maximum accepted clock skew, in seconds, between the signed timestamp and now.

View Source
const SignatureHeader = "Silon-Signature"

SignatureHeader is the HTTP header carrying the webhook signature on every delivery to a subscribed endpoint.

View Source
const Version = "0.1.0"

Version is the SDK release version, reported in the User-Agent header.

Variables

This section is empty.

Functions

func Bool

func Bool(v bool) *bool

Bool returns a pointer to v, for optional bool params.

func Float

func Float(v float64) *float64

Float returns a pointer to v, for optional float64 params.

func Int

func Int(v int) *int

Int returns a pointer to v, for optional int params.

func IsAuthentication

func IsAuthentication(err error) bool

IsAuthentication reports whether err is an *APIError with HTTP status 401.

func IsBadRequest

func IsBadRequest(err error) bool

IsBadRequest reports whether err is an *APIError with HTTP status 400.

func IsConflict

func IsConflict(err error) bool

IsConflict reports whether err is an *APIError with HTTP status 409.

func IsGone

func IsGone(err error) bool

IsGone reports whether err is an *APIError with HTTP status 410.

func IsInternalServer

func IsInternalServer(err error) bool

IsInternalServer reports whether err is an *APIError with HTTP status >= 500.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is an *APIError with HTTP status 404.

func IsPermissionDenied

func IsPermissionDenied(err error) bool

IsPermissionDenied reports whether err is an *APIError with HTTP status 403.

func IsRateLimit

func IsRateLimit(err error) bool

IsRateLimit reports whether err is an *APIError with HTTP status 429. Check the error's RetryAfter for the advertised backoff.

func IsUnprocessableEntity

func IsUnprocessableEntity(err error) bool

IsUnprocessableEntity reports whether err is an *APIError with HTTP status 422.

func SignWebhookPayload

func SignWebhookPayload(secret string, payload []byte, ts int64) string

SignWebhookPayload produces a valid Silon-Signature value — useful in tests and mocks. When ts <= 0, the current time is used.

func String

func String(v string) *string

String returns a pointer to v, for optional string params.

func Time

func Time(v time.Time) *time.Time

Time returns a pointer to v, for optional time.Time params (e.g. SendAt).

func VerifyWebhookSignature

func VerifyWebhookSignature(payload []byte, header, secret string, tolerance int) bool

VerifyWebhookSignature reports whether header is a valid Silon-Signature for payload under the endpoint's whsec_ secret and within tolerance seconds of now. tolerance <= 0 skips the freshness check. The comparison is constant-time; a malformed header returns false.

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status code.
	StatusCode int
	// RequestID is the X-Request-Id response header, when present.
	RequestID string
	// ErrorType is the body's "type" discriminator (a slug for standard
	// errors, a documentation URL for inline problems).
	ErrorType string
	// Errors holds the normalized error entries.
	Errors []ErrorDetail
	// Body is the raw JSON error body; nil when the body was not valid
	// JSON. Useful for shapes carrying extra keys (e.g. the OTP-verify
	// failure's remaining_attempts).
	Body json.RawMessage
	// RetryAfter is the advertised backoff in seconds, parsed from the
	// Retry-After / RateLimit-Reset headers. Set on 429 responses only.
	RetryAfter *float64
	// Retryable mirrors the error body's top-level "retryable" bool: true
	// iff retrying the SAME request could ever succeed (HTTP 429, 5xx, or an
	// in-flight idempotency twin), false for every other 4xx (validation,
	// auth, permission, not-found, conflict, gone). It is read verbatim from
	// the body — never recomputed from the status code — and is nil when a
	// legacy / non-v1 error body omits the field.
	Retryable *bool
	// Message is the human-readable summary: "attr: detail" of the first
	// error, else its detail, else "HTTP <code>: <reason>".
	Message string
}

APIError is a non-2xx API response, normalized from both error body shapes the Silon API produces (standard {"type", "errors": [...]} and RFC 9457-style inline problems). Retrieve it with errors.As, or use the Is* predicate helpers (IsNotFound, IsRateLimit, ...).

func (*APIError) Error

func (e *APIError) Error() string

type AWSUsageReportParams

type AWSUsageReportParams struct {
	// Page is the 1-based page to fetch.
	Page *int
}

AWSUsageReportParams are the parameters for ReportsService.AWSUsage. Page, when non-nil, is sent as a query parameter (the request has no body).

type AuthService

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

AuthService signs up users, and holds the deprecated password login. Access it via Client.Auth.

func (*AuthService) Login deprecated

func (s *AuthService) Login(ctx context.Context, params LoginParams) (*LoginResult, error)

Login exchanges a username + password for a Bearer token (POST /api/v1/login/).

Deprecated: prefer a scoped sk_live_ API key created in the dashboard under Settings > API keys.

func (*AuthService) Signup

func (s *AuthService) Signup(ctx context.Context, params SignupParams) (*UserProfile, error)

Signup creates a new user account (POST /api/v1/signup/, 201 — the body is the created profile; throttled server-side).

type BatchAccepted

type BatchAccepted struct {
	// ID is the batch id. On the inline form it identifies the accepted
	// request (inline batches have no GET endpoint); on the file form it
	// IS the created bulk batch id — per-row status reads via
	// GET /api/v1/bulk/{id}/ (BulkService.Retrieve) and the bulk reports.
	ID string `json:"id"`

	// Object is "batch".
	Object string `json:"object"`

	// Livemode is false when the batch ran in test mode (an sk_test_
	// key): no row reaches a provider and nothing is billed.
	Livemode bool `json:"livemode"`

	// Status is the aggregate batch status on the file form ("queued",
	// or "scheduled" when the request carried SendAt); empty on the
	// inline form.
	Status string `json:"status,omitempty"`

	// RowCount (file form only) is the CSV's data-row count, present
	// only when cheaply known.
	RowCount *int `json:"row_count,omitempty"`

	// Messages holds the per-row envelopes, in request order. It is set
	// on the inline form only — nil on the file form, where rows expand
	// asynchronously through the bulk pipeline. Suppressed rows are
	// omitted (counted in Skipped.Suppressed instead).
	Messages []BatchMessage `json:"messages,omitempty"`

	// SkippedCount (inline form only) is how many rows were skipped
	// rather than queued; nil on the file form, where the breakdown
	// surfaces on the bulk read side (BulkService.Retrieve) once async
	// expansion runs — and on servers predating the field.
	SkippedCount *int `json:"skipped_count,omitempty"`

	// Skipped itemises SkippedCount per reason (inline form only; nil on
	// the file form and on servers predating the breakdown).
	Skipped *SkippedBreakdown `json:"skipped,omitempty"`
}

BatchAccepted is the 202 envelope from POST /api/v1/messages/batch/.

type BatchMessage

type BatchMessage struct {
	// ID is the row's message tracking id, individually pollable at
	// GET /api/v1/messages/{id}/ (MessagesService.Retrieve).
	ID string `json:"id"`

	// Object is "message".
	Object string `json:"object"`

	Channel string `json:"channel"`
	Status  string `json:"status"`
}

BatchMessage is one per-row envelope inside BatchAccepted.

type Broadcast

type Broadcast struct {
	// ID is the broadcast id ("br_" prefixed).
	ID string `json:"id"`

	// Channel is the channel slug the broadcast was sent on.
	Channel string `json:"channel"`

	// Livemode is false when the broadcast ran in test mode (an sk_test_
	// key) — its per-recipient statuses are simulated. Nil when the
	// server does not report a mode.
	Livemode *bool `json:"livemode,omitempty"`

	// TargetCount is the total number of recipient rows in the broadcast.
	TargetCount int `json:"target_count"`

	// Queued is how many rows are still queued (not yet sent).
	Queued int `json:"queued"`

	// Sent is how many rows were successfully sent.
	Sent int `json:"sent"`

	// Failed is how many rows failed to send.
	Failed int `json:"failed"`

	// StartedAt is the timestamp of the earliest recipient row.
	StartedAt *time.Time `json:"started_at,omitempty"`

	// CompletedAt is the timestamp of the last send once nothing is left
	// queued; nil while the broadcast is still in progress.
	CompletedAt *time.Time `json:"completed_at,omitempty"`

	// Status is "scheduled" before a SendAt broadcast dispatches
	// ("canceled" after a successful Cancel); once dispatched,
	// "completed" when nothing is queued, otherwise "in_progress"
	// ("failed" when a scheduled dispatch faulted before any recipient
	// row was created — such a broadcast never went out).
	Status string `json:"status"`

	// SendAt (scheduled broadcasts only) is when the broadcast will
	// dispatch.
	SendAt *time.Time `json:"send_at,omitempty"`
}

Broadcast is the body of GET /api/v1/broadcasts/{broadcast_id}/ — aggregate delivery counts for one broadcast.

type BroadcastAccepted

type BroadcastAccepted struct {
	// ID is the broadcast id ("br_" prefixed).
	ID string `json:"id"`

	// Object is "broadcast".
	Object string `json:"object"`

	// Livemode is false when the broadcast ran in test mode (an sk_test_
	// key): nothing reaches a provider and nothing is billed.
	Livemode bool `json:"livemode"`

	Channel string `json:"channel"`

	// Status is "queued", "scheduled" when the request carried SendAt,
	// or "canceled" on the envelope returned by Cancel.
	Status string `json:"status"`

	// TargetCount is the number of recipients targeted. The server may
	// report null (decoded as 0) on a scheduled envelope when the channel
	// resolves its audience at dispatch time.
	TargetCount int `json:"target_count"`

	// SkippedCount is the number of recipients skipped (duplicates,
	// unsubscribed, unreachable). Like TargetCount, null (decoded as 0)
	// on a scheduled envelope until the audience resolves at dispatch.
	SkippedCount int `json:"skipped_count"`

	// Skipped itemises SkippedCount per reason. Nil exactly when
	// TargetCount is null (a scheduled envelope whose audience resolves
	// at dispatch time) — and on servers predating the breakdown.
	Skipped *SkippedBreakdown `json:"skipped,omitempty"`
}

BroadcastAccepted is the 202 envelope from POST /api/v1/broadcasts/.

type BroadcastCreateParams

type BroadcastCreateParams struct {
	// Channel is required: "sms", "whatsapp", "email", "push", "web_push", ...
	Channel string

	// Audience is required and selects the recipients, e.g.
	// {"type": "client_group", "slug": ...},
	// {"type": "client_ids", "client_ids": [...]} or an inline ad-hoc list
	// {"type": "recipients", "recipients": [{"phone_number": ...},
	// {"email": ...}, {"client_id": ...}, ...]} (max 1,000 rows; duplicate
	// addresses are deduped into Skipped.Duplicate, suppressed recipients
	// skipped into Skipped.Suppressed — SkippedCount stays the sum).
	Audience map[string]any

	// Content is the message content, e.g. {"body": ...} and, for email,
	// {"subject": ...}.
	Content map[string]any

	// Template references a stored message template by slug, e.g.
	// {"slug": "order-shipped", "variables": {...}}. Pin an older immutable
	// revision with an optional integer "version": {"slug": ...,
	// "version": 2}; omit it to render the latest.
	Template map[string]any

	Provider    *string
	Sender      *string
	Application *string
	WidgetKey   *string
	Priority    *string
	TTL         *int

	// WhatsApp holds channel-specific options for WhatsApp sends.
	WhatsApp map[string]any

	// WhatsAppTemplate selects a WhatsApp template, e.g. {"name": ...,
	// "language": ..., "variables": {...}}.
	WhatsAppTemplate map[string]any

	// SendAt schedules the broadcast for a future moment, serialized
	// ISO-8601 with the value's own UTC offset (a time.Time always
	// carries one). Server rules: strictly in the future, at most 90 days
	// ahead — otherwise a 422 slug "send-at-invalid". The envelope
	// answers status "scheduled" and its ID is stable through dispatch;
	// cancel while still scheduled via BroadcastsService.Cancel. A
	// pre-formatted ISO-8601 string can be passed via
	// ExtraBody["send_at"] instead.
	SendAt *time.Time

	// IdempotencyKey is sent as the Idempotency-Key header. When empty, a
	// UUIDv4 is generated — the header is ALWAYS sent, and the same value
	// is replayed on every retry attempt, so a retry can never double-send.
	IdempotencyKey string

	// ExtraBody is merged into the request body last — an escape hatch
	// for fields this SDK version does not model.
	ExtraBody map[string]any
}

BroadcastCreateParams are the parameters for BroadcastsService.Create.

Channel and Audience are required. All other fields are optional; nil fields are omitted from the request JSON. Fields not covered here can be passed via ExtraBody, which is merged into the body last (overriding on key collision).

type BroadcastDeliveriesParams

type BroadcastDeliveriesParams struct {
	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

BroadcastDeliveriesParams are the optional cursor-pagination parameters for BroadcastsService.Deliveries. Nil fields are omitted from the query.

type BroadcastDelivery

type BroadcastDelivery struct {
	// ID is the delivery's tracking id (UUID string).
	ID string `json:"id"`

	// ClientID is the external client identifier for this recipient (may
	// be blank).
	ClientID string `json:"client_id"`

	// Status is the delivery status, e.g. "pending", "queued", "sent",
	// "failed".
	Status string `json:"status"`

	// SentAt is when the row was sent; nil if not yet sent.
	SentAt *time.Time `json:"sent_at,omitempty"`

	// Error is the failure detail if the delivery failed; nil otherwise.
	Error *string `json:"error,omitempty"`
}

BroadcastDelivery is one per-recipient delivery row for a broadcast.

type BroadcastsService

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

BroadcastsService creates broadcasts (one piece of content fanned out to an audience, POST /api/v1/broadcasts/) and inspects them: aggregate delivery counts and per-recipient delivery rows. Access it via Client.Broadcasts.

func (*BroadcastsService) Cancel

func (s *BroadcastsService) Cancel(ctx context.Context, broadcastID string) (*BroadcastAccepted, error)

Cancel cancels a broadcast that was created with SendAt and is still "scheduled" (POST /api/v1/broadcasts/{broadcast_id}/cancel/, 200). The returned envelope shows status "canceled"; a canceled broadcast never dispatches and emits a broadcast.canceled event (livemode-aware).

Cancel is idempotent by nature and sends NO Idempotency-Key: canceling an already-canceled broadcast answers the same 200 envelope again (no second event). Once dispatched (or for an immediate broadcast's id) the server answers 409 slug "not-cancellable"; an unknown id is a 404. Requires the broadcasts:send scope.

func (*BroadcastsService) Create

Create sends a broadcast — one piece of content fanned out to an audience — on any outbound channel (POST /api/v1/broadcasts/, 202).

An Idempotency-Key header is always sent (auto-generated UUIDv4 when params.IdempotencyKey is empty), which makes automatic retries safe. Requires the broadcasts:send scope.

func (*BroadcastsService) Deliveries

func (s *BroadcastsService) Deliveries(ctx context.Context, broadcastID string, params BroadcastDeliveriesParams) (*Page[BroadcastDelivery], error)

Deliveries lists per-recipient delivery rows for a broadcast (GET /api/v1/broadcasts/{broadcast_id}/deliveries/, cursor-paginated).

func (*BroadcastsService) Retrieve

func (s *BroadcastsService) Retrieve(ctx context.Context, broadcastID string) (*Broadcast, error)

Retrieve fetches aggregate delivery counts for a broadcast (GET /api/v1/broadcasts/{broadcast_id}/). The id resolves before AND after a scheduled dispatch: Status reads "scheduled", then the normal lifecycle.

type BulkBatch

type BulkBatch struct {
	// ID is the bulk batch id.
	ID int `json:"id"`

	// Filename is the stored CSV filename the batch was built from.
	Filename string `json:"filename"`

	// Success is how many recipients in this batch were sent/read.
	Success int `json:"success"`

	// Total is the total number of recipients in this batch.
	Total int `json:"total"`

	// Channels are the distinct channels used across the batch's
	// recipients.
	Channels []any `json:"channels,omitempty"`

	// CreatedAt is when the batch was created.
	CreatedAt *time.Time `json:"created_at,omitempty"`

	// SentAt is when the batch finished sending; nil until then.
	SentAt *time.Time `json:"sent_at,omitempty"`

	// Timezone is the IANA zone scheduled sends are interpreted in.
	Timezone string `json:"timezone,omitempty"`
}

BulkBatch is one row from GET /api/v1/bulk/.

type BulkBatchDetail

type BulkBatchDetail struct {
	BulkBatch

	// Provider is the provider that handled the batch, if uniform.
	Provider string `json:"provider,omitempty"`

	// Applications are push app names used by the batch (push channel).
	Applications []any `json:"applications,omitempty"`

	// WebApplications are Web Push widget names used (web_push channel).
	WebApplications []any `json:"web_applications,omitempty"`

	// Sender is the from-identity the batch was sent from, if set.
	Sender string `json:"sender,omitempty"`

	// Template lists the templates used across the batch's recipients.
	Template []any `json:"template,omitempty"`

	// Subject is the subject line (email channel).
	Subject string `json:"subject,omitempty"`

	// Messages are the distinct rendered message bodies in the batch.
	Messages []any `json:"messages,omitempty"`

	// ScheduledAt is when the batch is/was scheduled to send.
	ScheduledAt *time.Time `json:"scheduled_at,omitempty"`

	// Recipients holds the per-recipient rows.
	Recipients []BulkRecipient `json:"recipients,omitempty"`
}

BulkBatchDetail is the full batch detail from GET /api/v1/bulk/{id}/.

type BulkFile

type BulkFile struct {
	// Name is the saved filename — pass it as BulkFile to
	// BulkService.Send.
	Name string `json:"name"`

	// Size is the file size in bytes.
	Size int `json:"size"`

	// ModifiedAt is when the file was last written.
	ModifiedAt time.Time `json:"modified_at"`
}

BulkFile is one saved CSV listed by GET /api/v1/bulk/files/.

type BulkFileList

type BulkFileList struct {
	// Count is the number of saved CSVs.
	Count int `json:"count"`

	// Results are the saved CSVs available for bulk sends.
	Results []BulkFile `json:"results"`
}

BulkFileList is the body of GET /api/v1/bulk/files/.

type BulkFileUpload

type BulkFileUpload struct {
	// Name is the UUID-based saved filename; pass it as BulkFile to
	// BulkService.Send.
	Name string `json:"name"`

	// OriginalFilename is the filename you uploaded, kept for your own
	// records.
	OriginalFilename string `json:"original_filename"`

	// Size is the file size in bytes.
	Size int `json:"size"`

	// ModifiedAt is when the upload was saved.
	ModifiedAt time.Time `json:"modified_at"`
}

BulkFileUpload is the 201 body of POST /api/v1/bulk/files/.

type BulkFileUploadParams

type BulkFileUploadParams struct {
	// Path reads the CSV from a file on disk.
	Path string

	// Content is the raw CSV bytes.
	Content []byte

	// Reader streams the CSV; it is read fully at call time.
	Reader io.Reader

	// Filename overrides the multipart filename.
	Filename string
}

BulkFileUploadParams are the parameters for BulkFilesService.Upload.

Exactly one content source is required: Path (read from disk), Content (raw CSV bytes), or Reader (streamed). The multipart filename defaults to Filename, else the source's own name (Path's base name, or Name() of a Reader such as *os.File), else "recipients.csv".

type BulkFilesService

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

BulkFilesService manages the saved CSVs bulk sends are built from (/api/v1/bulk/files/). Access it via Client.Bulk.Files.

func (*BulkFilesService) List

List fetches the saved CSVs available for bulk sends (GET /api/v1/bulk/files/).

func (*BulkFilesService) Upload

Upload saves a CSV for later bulk sends (POST /api/v1/bulk/files/, multipart form field "file", part content type text/csv). Pass the returned Name as BulkSendParams.BulkFile.

type BulkRecipient

type BulkRecipient struct {
	ID          int    `json:"id"`
	ClientID    string `json:"client_id,omitempty"`
	PhoneNumber string `json:"phone_number,omitempty"`
	Email       string `json:"email,omitempty"`
	Status      string `json:"status,omitempty"`
	Error       string `json:"error,omitempty"`
}

BulkRecipient is a per-recipient row embedded in a bulk batch detail.

type BulkRecipientDetail

type BulkRecipientDetail struct {
	// ID is the recipient row id.
	ID int `json:"id"`

	// FileName is the CSV filename this recipient came from.
	FileName string `json:"file_name,omitempty"`

	// Status is the delivery status of this recipient row.
	Status string `json:"status,omitempty"`

	// Channel this row was sent on, e.g. "sms" / "whatsapp".
	Channel string `json:"channel,omitempty"`

	// Provider that handled the send, if any.
	Provider string `json:"provider,omitempty"`

	// Application is the push app name (push channel), else empty.
	Application string `json:"application,omitempty"`

	// WebApp is the Web Push widget name (web_push channel), else empty.
	WebApp string `json:"web_app,omitempty"`

	// Sender is the from-identity this row was sent from, if set.
	Sender string `json:"sender,omitempty"`

	// Template used to render the message, if any.
	Template string `json:"template,omitempty"`

	// Subject line (email channel).
	Subject string `json:"subject,omitempty"`

	// Messages is the rendered message body sent to this recipient.
	Messages string `json:"messages,omitempty"`

	CreatedAt   *time.Time `json:"created_at,omitempty"`
	ScheduledAt *time.Time `json:"scheduled_at,omitempty"`
	SentAt      *time.Time `json:"sent_at,omitempty"`
}

BulkRecipientDetail is the body of GET /api/v1/bulk/recipient/{id}/.

type BulkRecipientsService

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

BulkRecipientsService looks up individual bulk recipient rows (/api/v1/bulk/recipient/). Access it via Client.Bulk.Recipients.

func (*BulkRecipientsService) Retrieve

func (s *BulkRecipientsService) Retrieve(ctx context.Context, recipientID int) (*BulkRecipientDetail, error)

Retrieve fetches one bulk recipient row by id (GET /api/v1/bulk/recipient/{id}/).

type BulkSendParams

type BulkSendParams struct {
	// Recipients are inline recipient rows; each is a column->value map.
	Recipients []map[string]any

	// BulkFile is the filename of a CSV previously saved via
	// BulkFilesService.Upload.
	BulkFile *string

	// Channel is the default channel(s), comma-separated
	// (e.g. "sms,whatsapp").
	Channel *string

	// Message is the inline message body, when not using a template.
	Message *string

	// Template is a message template slug.
	Template *string

	// Subject is the subject line (email channel).
	Subject *string

	// Sender overrides the from-identity (channel/provider dependent).
	Sender *string

	// Group targets a client group.
	Group *string

	// Application is the app slug for the push channel.
	Application *string

	// WebApplication is the widget slug for the web_push channel.
	WebApplication *string

	// Language is the two-letter code used to render the template.
	Language *string

	// Files are attachment names to include with each message.
	Files []string

	// Name is the base name for the generated CSV when sending inline
	// recipients.
	Name *string

	// Expire expires undelivered messages.
	Expire *bool

	// RemoveDuplicates drops duplicate recipients before sending.
	RemoveDuplicates *bool

	// ScheduledAt schedules the batch ("YYYY-MM-DD HH:MM"), interpreted
	// in Timezone.
	ScheduledAt *string

	// Timezone is the IANA zone ScheduledAt is interpreted in.
	Timezone *string

	// Provider is sent as a query parameter.
	Provider *string

	// WhatsAppTemplate is the WhatsApp template name (query parameter).
	WhatsAppTemplate *string

	// WhatsAppTemplateLanguage is the template language (query parameter).
	WhatsAppTemplateLanguage *string

	// WhatsAppTemplateVariables are the template variables, JSON-encoded
	// (query parameter).
	WhatsAppTemplateVariables *string
}

BulkSendParams are the parameters for BulkService.Send.

Exactly one of Recipients (inline rows) or BulkFile (a saved CSV name from BulkFilesService.Upload) is required. Nil fields are omitted from the request. Provider and the WhatsAppTemplate* fields are sent as query parameters, everything else in the JSON body.

type BulkSendResult

type BulkSendResult struct {
	// OK is 1 on success, 0 on failure.
	OK int `json:"ok"`

	// Message is a human-readable confirmation.
	Message string `json:"message"`

	// BulkID is the id of the created bulk batch.
	BulkID int `json:"bulk_id"`

	// Queued is how many recipients were queued for delivery.
	Queued int `json:"queued"`

	// Failed is how many recipients failed validation.
	Failed int `json:"failed"`

	// Filename is the stored CSV filename the batch was built from.
	Filename string `json:"filename"`
}

BulkSendResult is the success body of POST /api/v1/bulk/send/.

type BulkService

type BulkService struct {

	// Files manages the saved CSVs bulk sends are built from.
	Files *BulkFilesService

	// Recipients looks up individual bulk recipient rows.
	Recipients *BulkRecipientsService
	// contains filtered or unexported fields
}

BulkService runs bulk (CSV) sends: batches, saved files, and per-recipient rows. Access it via Client.Bulk.

func (*BulkService) List

func (s *BulkService) List(ctx context.Context) ([]BulkBatch, error)

List fetches past bulk batches (GET /api/v1/bulk/ — the API returns a bare JSON array, not a paginated envelope).

func (*BulkService) Retrieve

func (s *BulkService) Retrieve(ctx context.Context, bulkID int) (*BulkBatchDetail, error)

Retrieve fetches one bulk batch with its per-recipient rows (GET /api/v1/bulk/{id}/).

func (*BulkService) Send deprecated

func (s *BulkService) Send(ctx context.Context, params BulkSendParams) (*BulkSendResult, error)

Send starts a bulk batch (POST /api/v1/bulk/send/).

Supply recipients either inline via params.Recipients (each row a column->value map) or by referencing a previously uploaded CSV via params.BulkFile — exactly one of the two, or a client-side *Error is returned.

Deprecated: use MessagesService.SendBatch — inline rows via Messages, saved CSVs via File (upload with BulkFilesService.Upload, which stays current). This endpoint's behavior is frozen; there is no removal date.

type BulksReportParams

type BulksReportParams struct {
	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string
}

BulksReportParams are the parameters for ReportsService.Bulks. All fields are optional; nil fields are omitted from the request JSON.

type ChannelsReportParams

type ChannelsReportParams struct {
	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// ChannelName filters to the named channels.
	ChannelName []string
}

ChannelsReportParams are the parameters for ReportsService.Channels. All fields are optional; nil fields are omitted from the request JSON.

type Client

type Client struct {

	// Messages sends messages on any channel and looks up delivery status.
	Messages *MessagesService

	// Broadcasts inspects audience fan-outs created by Messages.Send:
	// aggregate counts and per-recipient delivery rows.
	Broadcasts *BroadcastsService

	// OTP sends and verifies one-time passwords.
	OTP *OTPService

	// Clients manages CRM client profiles.
	Clients *ClientsService

	// ClientGroups manages CRM client groups (broadcast audiences).
	ClientGroups *ClientGroupsService

	// Bulk runs bulk (CSV) sends: batches, saved files (Bulk.Files), and
	// per-recipient rows (Bulk.Recipients).
	Bulk *BulkService

	// Events reads the event stream your webhook endpoints are fed from.
	Events *EventsService

	// WebhookEndpoints manages outbound webhook subscriptions.
	WebhookEndpoints *WebhookEndpointsService

	// Suppressions manages the workspace's do-not-contact list, enforced
	// on every send path.
	Suppressions *SuppressionsService

	// Reports runs activity reports (messages, channels, clients, users,
	// bulks, subscriptions, AWS usage) and provider balance lookups.
	Reports *ReportsService

	// WhatsAppTemplates lists approved WhatsApp templates.
	WhatsAppTemplates *WhatsAppTemplatesService

	// Templates manages slug-keyed message templates with an immutable
	// version spine.
	Templates *TemplatesService

	// Push registers mobile / web push devices and reads the legacy
	// native notification feeds.
	Push *PushService

	// Profile reads and updates the authenticated user's own profile.
	Profile *ProfileService

	// Auth signs up users, and holds the deprecated password login.
	Auth *AuthService
	// contains filtered or unexported fields
}

Client is the Silon API client. Construct it with NewClient; the zero value is not usable. A Client is safe for concurrent use.

func NewClient

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

NewClient builds a Client from functional options with environment fallbacks. It fails fast (returning the base *Error) when no API key or base URL can be resolved.

Resolution order:

  • API key: WithAPIKey, else SILON_API_KEY. Required.
  • Base URL: WithBaseURL, else SILON_BASE_URL, else WithWorkspace -> https://<workspace>.silon.tech, else SILON_WORKSPACE (same expansion). Required; trailing slash stripped.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the resolved API base URL (no trailing slash).

type ClientCreateParams

type ClientCreateParams struct {
	// ClientID is required: your stable identifier for the contact.
	ClientID string

	FirstName       *string
	LastName        *string
	Email           *string
	PhoneNumber     *string
	CivilID         *string
	Notes           *string
	DefaultLanguage *string
	DefaultChannel  *string
}

ClientCreateParams are the parameters for ClientsService.Create. Only ClientID is required; nil fields are omitted from the request JSON.

type ClientGroup

type ClientGroup struct {
	// ID is the server-assigned numeric id of the group.
	ID int `json:"id"`

	Name string `json:"name"`

	// Slug is the URL-safe identifier — pass it as audience.slug with
	// audience.type "client_group" to broadcast to this group.
	Slug string `json:"slug"`

	// IsActive is false for groups kept but excluded from broadcast
	// targeting.
	IsActive bool `json:"is_active"`

	// Clients holds the full member profiles (read-only). To change
	// membership, write ClientIDs on create/update/replace.
	Clients []ClientProfile `json:"clients,omitempty"`
}

ClientGroup is a CRM client group (/api/v1/crm/groups/).

type ClientGroupCreateParams

type ClientGroupCreateParams struct {
	// Name is the human-readable group name.
	Name string

	// Slug is the URL-safe identifier used as audience.slug when
	// broadcasting to the group.
	Slug string

	// ClientIDs is the write-only membership list: the client_ids that
	// make up the group.
	ClientIDs []string

	// IsActive set to false excludes the group from broadcast targeting.
	IsActive *bool
}

ClientGroupCreateParams are the parameters for ClientGroupsService.Create. Name and Slug are required; nil fields are omitted from the request JSON.

type ClientGroupListParams

type ClientGroupListParams struct {
	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size (default 50, max 100).
	Limit *int
}

ClientGroupListParams paginate ClientGroupsService.ListPage. Nil fields are omitted from the query.

type ClientGroupReplaceParams

type ClientGroupReplaceParams struct {
	Name string
	Slug string

	// ClientIDs is the complete membership; an empty non-nil slice
	// empties the group.
	ClientIDs []string

	IsActive *bool
}

ClientGroupReplaceParams are the parameters for ClientGroupsService.Replace (PUT) — the full new state of the group.

type ClientGroupUpdateParams

type ClientGroupUpdateParams struct {
	Name *string
	Slug *string

	// ClientIDs replaces the group membership when non-nil (write-only).
	ClientIDs []string

	IsActive *bool
}

ClientGroupUpdateParams are the parameters for ClientGroupsService.Update (PATCH). Only non-nil fields are sent; ClientIDs, when non-nil, REPLACES the whole membership.

type ClientGroupsService

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

ClientGroupsService manages CRM client groups (/api/v1/crm/groups/). Access it via Client.ClientGroups.

func (*ClientGroupsService) Create

Create adds a client group (POST /api/v1/crm/groups/).

func (*ClientGroupsService) Delete

func (s *ClientGroupsService) Delete(ctx context.Context, groupID int) error

Delete removes a client group (DELETE /api/v1/crm/groups/{id}/, 204 on success). Member client profiles are not deleted.

func (*ClientGroupsService) List deprecated

List fetches every client group in one request (GET /api/v1/crm/group/ — the deprecated singular route, which returns a bare JSON array rather than a paginated envelope).

Deprecated: the singular list route is frozen for back-compat. Use ListPage, which targets the canonical cursor-paginated /api/v1/crm/groups/ route and returns a *Page[ClientGroup] you can walk (or drain lazily with Page.All). This List remains source- and behavior-compatible and is not going away, but new code should prefer ListPage.

func (*ClientGroupsService) ListPage

ListPage returns one cursor-paginated page of client groups, newest first by id (GET /api/v1/crm/groups/ — the canonical plural route). Walk further pages with Page.NextPage, or drain them all lazily with Page.All.

func (*ClientGroupsService) Replace

func (s *ClientGroupsService) Replace(ctx context.Context, groupID int, params ClientGroupReplaceParams) (*ClientGroup, error)

Replace fully replaces a client group (PUT /api/v1/crm/groups/{id}/).

func (*ClientGroupsService) Retrieve

func (s *ClientGroupsService) Retrieve(ctx context.Context, groupID int) (*ClientGroup, error)

Retrieve fetches one client group by id (GET /api/v1/crm/groups/{id}/).

func (*ClientGroupsService) Update

func (s *ClientGroupsService) Update(ctx context.Context, groupID int, params ClientGroupUpdateParams) (*ClientGroup, error)

Update partially updates a client group (PATCH /api/v1/crm/groups/{id}/). A non-nil ClientIDs replaces the whole membership.

type ClientListParams

type ClientListParams struct {
	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size (default 50, max 100).
	Limit *int
}

ClientListParams paginate ClientsService.ListPage. Nil fields are omitted from the query.

type ClientProfile

type ClientProfile struct {
	// ClientID is your stable identifier for this contact — the value
	// passed as to.client_id (or inside an audience) when sending a
	// message. Set once; immutable on update.
	ClientID string `json:"client_id"`

	FirstName   string `json:"first_name,omitempty"`
	LastName    string `json:"last_name,omitempty"`
	Email       string `json:"email,omitempty"`
	PhoneNumber string `json:"phone_number,omitempty"`

	// CivilID is the Kuwait Civil ID; nullable.
	CivilID *string `json:"civil_id,omitempty"`

	// Notes are free-text internal notes, never sent to the contact.
	Notes string `json:"notes,omitempty"`

	// DefaultLanguage is the two-letter code used to localise templated
	// sends ("en" / "ar").
	DefaultLanguage string `json:"default_language,omitempty"`

	// DefaultChannel is the preferred outbound channel for this contact
	// (e.g. "sms", "whatsapp", "email").
	DefaultChannel string `json:"default_channel,omitempty"`
}

ClientProfile is a CRM contact (/api/v1/crm/clients/).

type ClientUpdateParams

type ClientUpdateParams struct {
	FirstName       *string
	LastName        *string
	Email           *string
	PhoneNumber     *string
	CivilID         *string
	Notes           *string
	DefaultLanguage *string
	DefaultChannel  *string
}

ClientUpdateParams are the parameters for ClientsService.Update (PATCH, only non-nil fields are sent) and ClientsService.Replace (PUT). The client_id itself is immutable and set via the method argument.

type ClientsReportParams

type ClientsReportParams struct {
	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string

	// Language filters by the client's default language.
	Language []string

	// Device filters by device platform.
	Device []string

	// WebSubscription filters by web push subscription state.
	WebSubscription *string
}

ClientsReportParams are the parameters for ReportsService.Clients. All fields are optional; nil fields are omitted from the request JSON.

type ClientsService

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

ClientsService manages CRM client profiles (/api/v1/crm/clients/). Access it via Client.Clients.

func (*ClientsService) Create

Create adds a CRM client (POST /api/v1/crm/clients/).

func (*ClientsService) Delete

func (s *ClientsService) Delete(ctx context.Context, clientID string) error

Delete removes a CRM client (DELETE /api/v1/crm/clients/{client_id}/, 204 on success).

func (*ClientsService) List deprecated

func (s *ClientsService) List(ctx context.Context) ([]ClientProfile, error)

List fetches every CRM client in one request (GET /api/v1/crm/client/ — the deprecated singular route, which returns a bare JSON array rather than a paginated envelope).

Deprecated: the singular list route is frozen for back-compat. Use ListPage, which targets the canonical cursor-paginated /api/v1/crm/clients/ route and returns a *Page[ClientProfile] you can walk (or drain lazily with Page.All). This List remains source- and behavior-compatible and is not going away, but new code should prefer ListPage.

func (*ClientsService) ListPage

func (s *ClientsService) ListPage(ctx context.Context, params ClientListParams) (*Page[ClientProfile], error)

ListPage returns one cursor-paginated page of CRM clients, newest first by registration_date (GET /api/v1/crm/clients/ — the canonical plural route). Walk further pages with Page.NextPage, or drain them all lazily with Page.All.

func (*ClientsService) Replace

func (s *ClientsService) Replace(ctx context.Context, clientID string, params ClientUpdateParams) (*ClientProfile, error)

Replace fully replaces a CRM client (PUT /api/v1/crm/clients/{client_id}/). The client_id itself is immutable; omitted fields are reset server-side.

func (*ClientsService) Retrieve

func (s *ClientsService) Retrieve(ctx context.Context, clientID string) (*ClientProfile, error)

Retrieve fetches one CRM client by its client_id (GET /api/v1/crm/clients/{client_id}/).

func (*ClientsService) Update

func (s *ClientsService) Update(ctx context.Context, clientID string, params ClientUpdateParams) (*ClientProfile, error)

Update partially updates a CRM client (PATCH /api/v1/crm/clients/{client_id}/) — only non-nil fields change.

type ConnectionError

type ConnectionError struct {
	// Timeout is true when the failure was a timeout.
	Timeout bool
	// contains filtered or unexported fields
}

ConnectionError means the request never produced an HTTP response (DNS, TLS, socket, timeout...). The underlying cause is available via errors.Unwrap / errors.Is / errors.As.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

Unwrap returns the underlying transport error.

type Error

type Error struct {
	Message string
}

Error is the base SDK error, used for configuration, client-side validation, and response-parse failures. API failures use *APIError and transport failures *ConnectionError.

func (*Error) Error

func (e *Error) Error() string

type ErrorDetail

type ErrorDetail struct {
	// Code is the machine-readable error code (e.g. "required").
	Code string `json:"code"`
	// Detail is the human-readable description.
	Detail string `json:"detail"`
	// Attr is the request field the error applies to, when field-specific.
	Attr *string `json:"attr"`
}

ErrorDetail is one normalized error entry from an API error response.

type Event

type Event struct {
	// ID is the opaque event id, "evt_" prefixed.
	ID string `json:"id"`

	Object string `json:"object,omitempty"`

	// Type is "message.delivered" / "message.failed" / "broadcast.completed".
	Type string `json:"type"`

	// Livemode is false when the event was produced by a test-mode
	// (sk_test_) send; true for live traffic.
	Livemode bool `json:"livemode"`

	APIVersion string     `json:"api_version,omitempty"`
	Created    *time.Time `json:"created,omitempty"`
	Data       EventData  `json:"data,omitempty"`
}

Event is an event envelope — the exact JSON returned by the Events API and POSTed to subscribed webhook endpoints.

func ConstructWebhookEvent

func ConstructWebhookEvent(payload []byte, header, secret string, tolerance int) (*Event, error)

ConstructWebhookEvent verifies the signature and parses payload into an *Event. It returns a *WebhookSignatureVerificationError when the signature is missing, stale, or does not match, and the base *Error when the payload is not valid JSON. Typical usage in an http.Handler:

body, _ := io.ReadAll(r.Body) // raw bytes, not parsed JSON
event, err := silon.ConstructWebhookEvent(
	body, r.Header.Get(silon.SignatureHeader),
	os.Getenv("SILON_WEBHOOK_SECRET"), silon.DefaultWebhookTolerance,
)

type EventData

type EventData struct {
	ID          *string    `json:"id,omitempty"`
	Object      *string    `json:"object,omitempty"`
	Channel     *string    `json:"channel,omitempty"`
	Recipient   *string    `json:"recipient,omitempty"`
	ClientID    *string    `json:"client_id,omitempty"`
	Status      *string    `json:"status,omitempty"`
	Error       *string    `json:"error,omitempty"`
	BroadcastID *string    `json:"broadcast_id,omitempty"`
	Provider    *string    `json:"provider,omitempty"`
	ExternalID  *string    `json:"external_id,omitempty"`
	SentAt      *time.Time `json:"sent_at,omitempty"`
	CreatedAt   *time.Time `json:"created_at,omitempty"`

	// Livemode is false when the underlying send ran in test mode.
	Livemode *bool `json:"livemode,omitempty"`

	// broadcast.completed only.
	TargetCount *int `json:"target_count,omitempty"`
	Sent        *int `json:"sent,omitempty"`
	Failed      *int `json:"failed,omitempty"`
}

EventData is the "data" payload carried inside an event envelope.

The shape varies by the envelope's Type — message.delivered / message.failed carry a settled-message snapshot, while broadcast.completed carries aggregate counts. Every field is therefore optional; branch on the parent envelope's Type.

type EventListParams

type EventListParams struct {
	// Type filters to one event type, e.g. "message.failed".
	Type *string

	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

EventListParams filter and paginate EventsService.List. Nil fields are omitted from the query.

type EventsService

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

EventsService reads the event stream your webhook endpoints are fed from (newest first). Access it via Client.Events.

func (*EventsService) List

func (s *EventsService) List(ctx context.Context, params EventListParams) (*Page[Event], error)

List pages through past events, newest first (GET /api/v1/events — no trailing slash; cursor-paginated).

func (*EventsService) Retrieve

func (s *EventsService) Retrieve(ctx context.Context, eventID string) (*Event, error)

Retrieve fetches one event by id (GET /api/v1/events/{event_id} — no trailing slash).

type LoginParams

type LoginParams struct {
	// Username is the account email.
	Username string

	// Password is the account password.
	Password string
}

LoginParams are the parameters for the deprecated AuthService.Login.

type LoginResult

type LoginResult struct {
	// Token is a Bearer token — prefer a scoped sk_live_ API key
	// instead.
	Token string `json:"token"`
}

LoginResult is the body of the deprecated POST /api/v1/login/.

type MessageAccepted

type MessageAccepted struct {
	// ID is the tracking id for the message, or the broadcast id.
	ID string `json:"id"`

	// Object is "message" for a single recipient, "broadcast" for an
	// audience fan-out.
	Object string `json:"object"`

	// Livemode is false when the request ran in test mode (an sk_test_
	// key): nothing reaches a provider and nothing is billed.
	Livemode bool `json:"livemode"`

	Channel string `json:"channel"`

	// Status is "queued", "scheduled" when the request carried SendAt,
	// or "canceled" on the envelope returned by Cancel.
	Status string `json:"status"`

	// TargetCount (broadcast only) is the number of recipients targeted;
	// nil on a scheduled envelope when the channel resolves its audience
	// at dispatch time.
	TargetCount *int `json:"target_count,omitempty"`

	// SkippedCount (broadcast only) is the number of recipients skipped
	// (suppressed / duplicate / unreachable).
	SkippedCount *int `json:"skipped_count,omitempty"`

	// Skipped (broadcast only) itemises SkippedCount per reason. Nil on
	// single-recipient envelopes, on a scheduled envelope whose audience
	// resolves at dispatch time, and on servers predating the breakdown.
	Skipped *SkippedBreakdown `json:"skipped,omitempty"`
}

MessageAccepted is the 202 envelope from POST /api/v1/messages/.

type MessageBatchParams

type MessageBatchParams struct {
	// Messages holds 1-500 inline free-form rows, each the same shape as
	// a MessageSendParams body minus Audience (rows are single-recipient
	// by definition — a row carrying "audience" fails the batch with a
	// per-index 422 pointing at POST /api/v1/broadcasts/). "to" is
	// required per row; a row's own "channel" overrides the top-level
	// default Channel (one of the two must yield a channel); the content
	// fields ("content", "template", "whatsapp_template", ...) are the
	// same as on a single send. Rows are sent verbatim.
	Messages []map[string]any

	// File is the saved server-side CSV name returned by
	// BulkFilesService.Upload (POST /api/v1/bulk/files/). Rows expand
	// asynchronously through the bulk pipeline; the request-level
	// defaults below apply to every CSV row that lacks its own column.
	// An unknown name is a 404 slug "file-not-found".
	File *string

	// Channel is the default channel applied to rows that do not carry
	// their own: "sms", "whatsapp", "email", "push", "web_push", ...
	Channel *string

	// Content is the default message content, e.g. {"body": ...} and,
	// for email, {"subject": ...}.
	Content map[string]any

	// Template is the default stored-message-template reference by slug,
	// e.g. {"slug": "order-shipped", "variables": {...}}. Pin an older
	// immutable revision with an optional integer "version": {"slug": ...,
	// "version": 2}; omit it to render the latest.
	Template map[string]any

	Provider    *string
	Sender      *string
	Application *string
	WidgetKey   *string
	Priority    *string
	TTL         *int

	// WhatsApp holds default channel-specific options for WhatsApp rows.
	WhatsApp map[string]any

	// WhatsAppTemplate is the default WhatsApp template selector, e.g.
	// {"name": ..., "language": ..., "variables": {...}}.
	WhatsAppTemplate map[string]any

	// SendAt schedules the batch — FILE form only — serialized ISO-8601
	// with the value's own UTC offset (a time.Time always carries one).
	// Server rules: strictly in the future, at most 90 days ahead —
	// otherwise a 422 slug "send-at-invalid". The envelope answers status
	// "scheduled"; rows expand and send at dispatch time. With inline
	// Messages the server rejects it with a 422 slug "batch-invalid" (no
	// batch cancel resource exists by design — schedule rows individually
	// via MessagesService.Send, which supports per-message Cancel).
	SendAt *time.Time

	// IdempotencyKey is sent as the Idempotency-Key header. When empty, a
	// UUIDv4 is generated — the header is ALWAYS sent, and the same value
	// is replayed on every retry attempt, so a retry can never double-send.
	IdempotencyKey string

	// ExtraBody is merged into the request body last — an escape hatch
	// for fields this SDK version does not model.
	ExtraBody map[string]any
}

MessageBatchParams are the parameters for MessagesService.SendBatch.

Exactly one of Messages (inline rows) or File (a saved CSV name from BulkFilesService.Upload) is required. All other fields are optional row DEFAULTS — applied to every row (or CSV column) that does not carry its own value; a row value always wins (row value > request default > none). Nil fields are omitted from the request JSON. Fields not covered here can be passed via ExtraBody, which is merged into the body last (overriding on key collision).

type MessageSendParams

type MessageSendParams struct {
	// Channel is required: "sms", "whatsapp", "email", "push", "web_push", ...
	Channel string

	// To targets a single recipient, e.g. {"client_id": ...},
	// {"phone_number": ...}, {"email": ...} or {"device_token": ...}.
	To map[string]any

	// Audience targets a broadcast, e.g. {"type": "client_group",
	// "slug": ...} or {"type": "client_ids", "client_ids": [...]}.
	Audience map[string]any

	// Content is the message content, e.g. {"body": ...} and, for email,
	// {"subject": ...}.
	Content map[string]any

	// Template references a stored message template by slug, e.g.
	// {"slug": "order-shipped", "variables": {...}}. Pin an older immutable
	// revision with an optional integer "version": {"slug": ...,
	// "version": 2}; omit it to render the latest. An unknown pinned
	// version is a 422 slug "template-version-not-found".
	Template map[string]any

	Provider    *string
	Sender      *string
	Application *string
	WidgetKey   *string
	Priority    *string
	TTL         *int

	// WhatsApp holds channel-specific options for WhatsApp sends.
	WhatsApp map[string]any

	// WhatsAppTemplate selects a WhatsApp template, e.g. {"name": ...,
	// "language": ..., "variables": {...}}.
	WhatsAppTemplate map[string]any

	// SendAt schedules the send for a future moment, serialized ISO-8601
	// with the value's own UTC offset (a time.Time always carries one).
	// Server rules: strictly in the future, at most 90 days ahead —
	// otherwise a 422 slug "send-at-invalid". The envelope answers status
	// "scheduled" and its ID is stable through dispatch; cancel while
	// still scheduled via MessagesService.Cancel. A pre-formatted
	// ISO-8601 string can be passed via ExtraBody["send_at"] instead.
	SendAt *time.Time

	// OverrideSuppression set to silon.Bool(true) delivers even when the
	// recipient is on the suppression list (SuppressionsService) — for
	// transactional/legal sends, e.g. a receipt owed to an unsubscribed
	// customer. Single-recipient sends (To) ONLY. It requires the
	// suppressions:override scope, which is in no scope preset and must
	// be granted explicitly: without it the request is rejected with a
	// 403 slug "missing-scope"; sent alongside Audience (or on batches /
	// broadcasts) it is a 422 slug "override-not-allowed". An overridden
	// send proceeds and its delivery row is flagged
	// suppression_overridden: true.
	OverrideSuppression *bool

	// IdempotencyKey is sent as the Idempotency-Key header. When empty, a
	// UUIDv4 is generated — the header is ALWAYS sent, and the same value
	// is replayed on every retry attempt, so a retry can never double-send.
	IdempotencyKey string

	// ExtraBody is merged into the request body last — an escape hatch
	// for fields this SDK version does not model.
	ExtraBody map[string]any
}

MessageSendParams are the parameters for MessagesService.Send.

Exactly one of To (single recipient) or Audience (broadcast selector) is required. All other fields are optional; nil fields are omitted from the request JSON. Fields not covered here can be passed via ExtraBody, which is merged into the body last (overriding on key collision).

type MessageStatus

type MessageStatus struct {
	// ID is the message id the status was looked up by (same value as the
	// path id).
	ID string `json:"id"`

	// Object is always "message".
	Object string `json:"object,omitempty"`

	// Livemode is false when the send ran in test mode (an sk_test_
	// key) — its status transitions are simulated. Nil when the server
	// does not report a mode.
	Livemode *bool `json:"livemode,omitempty"`

	// Status is the aggregate lifecycle status: "scheduled" before a
	// SendAt message dispatches ("canceled" after a successful Cancel),
	// then "queued" while any row is in flight, then "sent" ("failed"
	// when every row failed). "delivered" never appears here — only in
	// Timeline.
	Status string `json:"status,omitempty"`

	// Channel is the channel slug the send was routed to (e.g. "sms",
	// "whatsapp"); nil when it cannot be derived from the delivery rows.
	Channel *string `json:"channel,omitempty"`

	// SendAt (scheduled sends only) is when the send will dispatch.
	SendAt *time.Time `json:"send_at,omitempty"`

	// Timeline is the ordered list of attested status transitions,
	// ascending by At. A single-recipient send carries the full journey
	// (queued -> sent -> delivered when the channel reports receipts); a
	// scheduled send answers [{Status: "scheduled", At}] until dispatch.
	Timeline []MessageTimelineEntry `json:"timeline,omitempty"`

	// EventID is a legacy alias of ID, still returned for backward
	// compatibility.
	//
	// Deprecated: use ID.
	EventID string `json:"event_id"`

	// IsSent is a legacy aggregate over the batch (true once every row has
	// left pending/queued without ending in failed).
	//
	// Deprecated: read the aggregate Status (or Timeline) instead.
	IsSent bool `json:"is_sent"`

	// Messages holds the legacy per-recipient rows.
	//
	// Deprecated: read Timeline (and, for a fan-out, BroadcastsService)
	// instead.
	Messages []MessageStatusItem `json:"messages"`
}

MessageStatus is the body of GET /api/v1/messages/{event_id}/.

type MessageStatusItem

type MessageStatusItem struct {
	ClientID    string `json:"client_id"`
	PhoneNumber string `json:"phone_number"`
	Email       string `json:"email"`
	IsRead      bool   `json:"is_read"`
	ReadCount   int    `json:"read_count"`
}

MessageStatusItem is one recipient row inside a message-status batch.

type MessageTimelineEntry

type MessageTimelineEntry struct {
	// Status is the lifecycle stage this entry attests: "scheduled",
	// "queued", "sent", "delivered", "failed" or "canceled". "delivered"
	// is per-recipient granularity that appears ONLY here (never as the
	// top-level MessageStatus.Status) and only on single-recipient sends
	// whose channel reports receipts.
	Status string `json:"status"`

	// At is when the transition happened.
	At *time.Time `json:"at,omitempty"`

	// Provider is the provider that carried the send, when known (e.g.
	// "twilio"); nil otherwise.
	Provider *string `json:"provider,omitempty"`
}

MessageTimelineEntry is one attested status transition in a message's Timeline, in ascending order by At.

type MessagesReportParams

type MessagesReportParams struct {
	// ReportType is required: "phone" (SMS/TTS/WhatsApp), "email",
	// "mobile" (push) or "web" (web push).
	ReportType string

	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string

	// Device filters by device platform.
	Device []string

	// Status filters by delivery status.
	Status []string

	// Template filters by message template ids.
	Template []int

	// Source filters by message source.
	Source []string
}

MessagesReportParams are the parameters for ReportsService.Messages. Only ReportType is required; nil fields are omitted from the request JSON.

type MessagesService

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

MessagesService sends messages on any channel (POST /api/v1/messages/) and looks up delivery status. Access it via Client.Messages.

func (*MessagesService) Cancel

func (s *MessagesService) Cancel(ctx context.Context, eventID string) (*MessageAccepted, error)

Cancel cancels a message that was created with SendAt and is still "scheduled" (POST /api/v1/messages/{event_id}/cancel/, 200) — eventID is the ID from the create envelope. The returned envelope shows status "canceled"; a canceled send never dispatches and emits a message.canceled event (livemode-aware).

Cancel is idempotent by nature and sends NO Idempotency-Key: canceling an already-canceled message answers the same 200 envelope again (no second event). Once dispatched (or for an immediate send's id) the server answers 409 slug "not-cancellable"; an unknown id is a 404. Requires the messages:send scope.

func (*MessagesService) Retrieve

func (s *MessagesService) Retrieve(ctx context.Context, eventID string) (*MessageStatus, error)

Retrieve looks up a queued/sent message batch by its tracking id (GET /api/v1/messages/{event_id}/). The id resolves before AND after a scheduled dispatch: Status reads "scheduled", then the normal lifecycle.

func (*MessagesService) Send

Send sends a message on any channel (POST /api/v1/messages/, 202).

Exactly one of params.To or params.Audience is required — a client-side *Error is returned otherwise. An Idempotency-Key header is always sent (auto-generated UUIDv4 when params.IdempotencyKey is empty), which makes automatic retries safe.

A single-recipient send to an address on the suppression list (SuppressionsService) is rejected with a 422 slug "recipient-suppressed" — see params.OverrideSuppression for the gated transactional bypass. An Audience fan-out skips suppressed recipients into Skipped.Suppressed instead.

func (*MessagesService) SendBatch

func (s *MessagesService) SendBatch(ctx context.Context, params MessageBatchParams) (*BatchAccepted, error)

SendBatch sends many independent, personalised messages in one call (POST /api/v1/messages/batch/, 202). Use it when every recipient gets different content; for one content fanned out to an audience, use BroadcastsService.Create.

Exactly one of params.Messages (up to 500 inline rows) or params.File (a saved CSV name from BulkFilesService.Upload) is required — a client-side *Error is returned otherwise (the server answers neither/ both with a 422 slug "batch-invalid"). Request-level fields (Channel, Content, Template, ...) act as row defaults on both forms; a row field or CSV column always wins.

Inline form: validation is all-or-nothing — the server validates every row through the same per-channel rules as Send before anything is queued, and any invalid row fails the whole batch with a 422 whose Attr carries a per-index path (e.g. "messages[3].to.phone_number"). An empty list is a 422 slug "batch-empty"; more than 500 rows is a 422 slug "batch-too-large". The 202 carries per-row envelopes in Messages, each id individually pollable via Retrieve.

File form: rows expand asynchronously through the bulk pipeline, so the 202 is the aggregate envelope only — Messages is nil, Status is "queued", and the batch ID is the created bulk batch id (per-row status via BulkService.Retrieve and the bulk reports). An unknown file name is a 404 slug "file-not-found"; defaults the bulk pipeline cannot honor are rejected with a 422 slug "batch-invalid".

An Idempotency-Key header is always sent (auto-generated UUIDv4 when params.IdempotencyKey is empty), which makes automatic retries safe — a replay returns the stored body: identical per-row ids on the inline form, the stored aggregate envelope on the file form. Requires the messages:send scope.

type OTPSendParams

type OTPSendParams struct {
	// Purpose names a configured OTP purpose on the tenant (e.g.
	// "login"); it decides the delivery channel, code shape and expiry.
	Purpose string

	// To targets the recipient: exactly one of "client_id" /
	// "phone_number" / "email".
	To map[string]any

	// IdempotencyKey is sent as the Idempotency-Key header. When empty, a
	// UUIDv4 is generated — the header is ALWAYS sent, and the same value
	// is replayed on every retry attempt, so a retry can never double-send.
	IdempotencyKey string
}

OTPSendParams are the parameters for OTPService.Send.

type OTPSendResult

type OTPSendResult struct {
	// OTPID is the opaque id for this OTP; pass it back to
	// OTPService.Verify.
	OTPID string `json:"otp_id"`

	// ExpiresAt is when the code expires. Verifying after this returns a
	// 410 *APIError (IsGone).
	ExpiresAt time.Time `json:"expires_at"`

	// Channel is the channel the code was dispatched over (decided by the
	// purpose), e.g. "sms".
	Channel string `json:"channel"`

	// Livemode is false when the OTP was issued by a test-mode (sk_test_)
	// request — the code is never dispatched and only the magic code
	// "000000" verifies.
	Livemode bool `json:"livemode"`

	// TaskIDs are tracking ids for the dispatched send(s); usually one.
	TaskIDs []string `json:"task_ids,omitempty"`
}

OTPSendResult is the 202 body of POST /api/v1/otp/send/.

type OTPService

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

OTPService sends and verifies one-time passwords. Access it via Client.OTP.

func (*OTPService) Send

func (s *OTPService) Send(ctx context.Context, params OTPSendParams) (*OTPSendResult, error)

Send dispatches a one-time password (POST /api/v1/otp/send/, 202). The recipient in params.To must contain exactly one of "client_id" / "phone_number" / "email"; the delivery channel is decided by the configured purpose. An Idempotency-Key header is always sent (auto-generated UUIDv4 when params.IdempotencyKey is empty), which makes automatic retries safe.

func (*OTPService) Verify

func (s *OTPService) Verify(ctx context.Context, params OTPVerifyParams) (*OTPVerifyResult, error)

Verify checks a code (POST /api/v1/otp/verify/). A wrong code returns a 400 *APIError (IsBadRequest) whose Body carries {"verified": false, "remaining_attempts": N}; an expired or locked OTP returns a 410 *APIError (IsGone).

type OTPVerifyParams

type OTPVerifyParams struct {
	// OTPID is the id returned by OTPService.Send.
	OTPID string

	// Code is the code the user entered.
	Code string
}

OTPVerifyParams are the parameters for OTPService.Verify.

type OTPVerifyResult

type OTPVerifyResult struct {
	// Verified is true when the code matched and the OTP is now consumed.
	Verified bool `json:"verified"`

	// Purpose is the purpose the OTP was issued for.
	Purpose string `json:"purpose"`

	// Livemode is false when the OTP was issued by a test-mode (sk_test_)
	// request.
	Livemode bool `json:"livemode"`

	// VerifiedAt is when verification succeeded.
	VerifiedAt time.Time `json:"verified_at"`
}

OTPVerifyResult is the 200 body of POST /api/v1/otp/verify/.

type Option

type Option func(*clientConfig)

Option configures a Client created by NewClient.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key. Falls back to the SILON_API_KEY environment variable when not provided.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API base URL explicitly (e.g. an on-prem deployment). A trailing slash is stripped.

func WithDefaultHeader

func WithDefaultHeader(key, value string) Option

WithDefaultHeader adds a header sent on every request. May be repeated; later values for the same key win.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a custom *http.Client — useful for TLS control (private CAs), proxies, or instrumentation. When set, the custom client's Timeout governs requests and WithTimeout is ignored.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a failed retryable request is retried (default 2). Zero disables retries.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the request timeout on the internally created HTTP client (default 30s). Ignored when WithHTTPClient is supplied — the custom client's own Timeout governs then.

func WithWorkspace

func WithWorkspace(workspace string) Option

WithWorkspace derives the base URL from a workspace slug: https://<workspace>.silon.tech. An explicit base URL (WithBaseURL or SILON_BASE_URL) takes precedence.

type Page

type Page[T any] struct {
	// Results holds this page's items.
	Results []T

	// Next is the opaque next-page URL advertised by the API (nil on the
	// last page). Only its query parameters are ever used.
	Next *string

	// Previous is the opaque previous-page URL, when present.
	Previous *string
	// contains filtered or unexported fields
}

Page is one page of a cursor-paginated list endpoint ({"results": [...], "next": url|null, "previous": url|null}).

NextPage never follows the opaque "next" URL directly: only its query parameters are extracted and merged over the original parameters, and the original path is re-requested against the configured base URL, so a proxy-rewritten hostname cannot hijack pagination.

func (*Page[T]) All

func (p *Page[T]) All(ctx context.Context) iter.Seq2[T, error]

All returns an iterator that lazily walks every item on this page and all following pages, fetching each next page only when needed:

for event, err := range page.All(ctx) {
	if err != nil {
		return err
	}
	...
}

A page-fetch failure is yielded once as a non-nil error and ends the iteration.

func (*Page[T]) HasNextPage

func (p *Page[T]) HasNextPage() bool

HasNextPage reports whether a following page exists.

func (*Page[T]) NextPage

func (p *Page[T]) NextPage(ctx context.Context) (*Page[T], error)

NextPage fetches the following page. Calling it on the last page (HasNextPage() == false) returns an *Error.

type ProfileReplaceParams

type ProfileReplaceParams struct {
	Email       string
	FirstName   string
	LastName    string
	PhoneNumber string

	// CivilID is the Kuwait Civil ID; validated and must be unique when
	// present.
	CivilID *string

	// DefaultLanguage is the two-letter code (e.g. "en", "ar").
	DefaultLanguage *string
}

ProfileReplaceParams are the parameters for ProfileService.Replace (PUT) — the full new state of the profile. Email, FirstName, LastName and PhoneNumber are required; nil fields are omitted from the request JSON.

type ProfileService

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

ProfileService reads and updates the authenticated user's own profile (/api/v1/profile/). Access it via Client.Profile.

func (*ProfileService) Replace

Replace fully replaces the authenticated user's profile (PUT /api/v1/profile/).

func (*ProfileService) Retrieve

func (s *ProfileService) Retrieve(ctx context.Context) (*UserProfile, error)

Retrieve fetches the authenticated user's profile (GET /api/v1/profile/).

func (*ProfileService) Update

Update partially updates the authenticated user's profile (PATCH /api/v1/profile/) — only non-nil fields change.

type ProfileUpdateParams

type ProfileUpdateParams struct {
	Email       *string
	FirstName   *string
	LastName    *string
	PhoneNumber *string

	// CivilID is the Kuwait Civil ID; validated and must be unique when
	// present.
	CivilID *string

	// DefaultLanguage is the two-letter code (e.g. "en", "ar").
	DefaultLanguage *string
}

ProfileUpdateParams are the parameters for ProfileService.Update (PATCH) — only non-nil fields are sent.

type ProviderBalance

type ProviderBalance struct {
	// Balance is the upstream provider balance (provider-specific
	// format; may be a number). Empty string when the account has no
	// balance lookup.
	Balance string `json:"balance"`
}

ProviderBalance is the body of GET /api/v1/reports/balance/{slug}/.

type PushClientDevices

type PushClientDevices struct {
	ClientID   string `json:"client_id"`
	Slug       string `json:"slug"`
	DeviceID   string `json:"device_id,omitempty"`
	DeviceType string `json:"device_type"`

	KeepDevices *bool `json:"keep_devices,omitempty"`
}

PushClientDevices is the echo body of POST /api/v1/push/client/.

type PushMarkReadResult

type PushMarkReadResult struct {
	// AffectedRows is the number of notifications transitioned to read.
	AffectedRows int `json:"affected_rows"`
}

PushMarkReadResult is the body of POST /api/v1/push/read/.

type PushNotification

type PushNotification struct {
	Message string `json:"message"`
	Subject string `json:"subject"`

	// Date is when the notification was sent. The legacy feed returns
	// date-only values, so it stays a string.
	Date string `json:"date"`
}

PushNotification is one native push notification row from the legacy list endpoints.

type PushPlatform

type PushPlatform string

PushPlatform selects which native notification feed PushService.ListNotifications reads.

const (
	// PushPlatformAndroid reads the Android feed.
	PushPlatformAndroid PushPlatform = "android"

	// PushPlatformIOS reads the iOS feed.
	PushPlatformIOS PushPlatform = "ios"

	// PushPlatformCombined (the zero value) reads the merged feed.
	PushPlatformCombined PushPlatform = ""
)

type PushService

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

PushService registers mobile / web push devices and reads the legacy native notification feeds. Access it via Client.Push.

func (*PushService) ListNotifications deprecated

func (s *PushService) ListNotifications(ctx context.Context, slug string, platform PushPlatform) ([]PushNotification, error)

ListNotifications lists the native push notifications sent to an app (GET /api/v1/push/android/{slug}/, /api/v1/push/ios/{slug}/ or, for PushPlatformCombined, /api/v1/push/list/{slug}/ — the API returns a bare JSON array). An unknown platform returns a client-side *Error without any request being made.

Deprecated: the native push notification list endpoints are legacy; use the Events API / webhook endpoints for delivery visibility instead.

func (*PushService) MarkRead

func (s *PushService) MarkRead(ctx context.Context, slug string) (*PushMarkReadResult, error)

MarkRead marks an app's unread native push notifications as read (POST /api/v1/push/read/).

func (*PushService) SubscribeAndroid

func (s *PushService) SubscribeAndroid(ctx context.Context, params PushSubscribeAndroidParams) (*PushSubscribeResult, error)

SubscribeAndroid registers an Android device token for an app (POST /api/v1/subscribe/android/).

func (*PushService) SubscribeIOS

SubscribeIOS registers an iOS device token for an app (POST /api/v1/subscribe/ios/), optionally pinning the APNs environment.

func (*PushService) SubscribeWeb deprecated

SubscribeWeb registers a browser subscription for a client (POST /api/v1/webpush/client/).

Deprecated: POST /api/v1/webpush/client/ is a legacy endpoint; register web push subscriptions through the widget instead.

func (*PushService) UpsertDevices

func (s *PushService) UpsertDevices(ctx context.Context, params PushUpsertDevicesParams) (*PushClientDevices, error)

UpsertDevices registers (or prunes, with KeepDevices false) a client's push devices (POST /api/v1/push/client/).

type PushSubscribeAndroidParams

type PushSubscribeAndroidParams struct {
	// Slug is required: the application slug to register the device
	// under.
	Slug string

	// Token is the FCM push token issued by the platform.
	Token *string
}

PushSubscribeAndroidParams are the parameters for PushService.SubscribeAndroid. Nil fields are omitted from the request JSON.

type PushSubscribeIOSParams

type PushSubscribeIOSParams struct {
	// Slug is required: the application slug to register the device
	// under.
	Slug string

	// Token is the APNs push token issued by the platform.
	Token *string

	// Environment is the APNs environment the token was minted in,
	// driven by the iOS build's aps-environment entitlement.
	Environment *string
}

PushSubscribeIOSParams are the parameters for PushService.SubscribeIOS. Nil fields are omitted from the request JSON.

type PushSubscribeResult

type PushSubscribeResult struct {
	// Success is always 1 on success.
	Success int `json:"success"`
}

PushSubscribeResult is the success body of the device subscribe endpoints.

type PushSubscribeWebParams

type PushSubscribeWebParams struct {
	// ClientID is required: the client to attach the browser
	// subscription to.
	ClientID string

	// Slug is required: the Web Push widget slug the visitor subscribed
	// through.
	Slug string

	// SubscriptionInfo is the browser PushSubscription JSON (endpoint +
	// p256dh/auth keys), as a string.
	SubscriptionInfo *string
}

PushSubscribeWebParams are the parameters for the deprecated PushService.SubscribeWeb. Nil fields are omitted from the request JSON.

type PushUpsertDevicesParams

type PushUpsertDevicesParams struct {
	// ClientID is required: the client whose push devices to
	// register/prune.
	ClientID string

	// Slug is required: the application slug the device belongs to.
	Slug string

	// DeviceType is required: "ios", "android" or "huawei".
	DeviceType string

	// DeviceID is the device push token to register for the given
	// platform. Omit to prune only.
	DeviceID *string

	// KeepDevices false (the server default) removes the client's other
	// tokens for this platform/app.
	KeepDevices *bool
}

PushUpsertDevicesParams are the parameters for PushService.UpsertDevices. Nil fields are omitted from the request JSON.

type Report

type Report struct {
	// ReportData is the page of report rows.
	ReportData []map[string]any `json:"report_data"`

	// TotalItems is the total number of matching rows across all pages.
	TotalItems int `json:"total_items"`

	// TotalPages is the number of pages at the tenant's configured page
	// size.
	TotalPages int `json:"total_pages"`

	// Page is the 1-based page index contained in this response.
	Page int `json:"page"`

	// ReportType echoes the requested report_type, on the reports that
	// take one.
	ReportType string `json:"report_type,omitempty"`
}

Report is the common envelope returned by every POST /api/v1/reports/* endpoint.

Row columns in ReportData vary per report (and per report type), so rows stay generic maps.

type ReportsService

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

ReportsService runs activity reports and provider balance lookups (/api/v1/reports/...). Access it via Client.Reports.

func (*ReportsService) AWSUsage

func (s *ReportsService) AWSUsage(ctx context.Context, params AWSUsageReportParams) (*Report, error)

AWSUsage fetches AWS usage statistics (POST /api/v1/reports/aws-usage-statistics/ — the page is a query parameter and the request has no body).

func (*ReportsService) Balance

func (s *ReportsService) Balance(ctx context.Context, slug string) (*ProviderBalance, error)

Balance fetches the upstream balance for a provider account (GET /api/v1/reports/balance/{slug}/).

func (*ReportsService) Bulks

func (s *ReportsService) Bulks(ctx context.Context, params BulksReportParams) (*Report, error)

Bulks runs the bulk batches report (POST /api/v1/reports/bulks/).

func (*ReportsService) Channels

func (s *ReportsService) Channels(ctx context.Context, params ChannelsReportParams) (*Report, error)

Channels runs the per-channel activity report (POST /api/v1/reports/channels/).

func (*ReportsService) Clients

func (s *ReportsService) Clients(ctx context.Context, params ClientsReportParams) (*Report, error)

Clients runs the clients report (POST /api/v1/reports/clients/).

func (*ReportsService) Messages

func (s *ReportsService) Messages(ctx context.Context, params MessagesReportParams) (*Report, error)

Messages runs the messages activity report (POST /api/v1/reports/messages/).

func (*ReportsService) SpecificBulks

func (s *ReportsService) SpecificBulks(ctx context.Context, params SpecificBulksReportParams) (*Report, error)

SpecificBulks reports on a single bulk batch (POST /api/v1/reports/specific-bulks/).

func (*ReportsService) Subscriptions

func (s *ReportsService) Subscriptions(ctx context.Context, params SubscriptionsReportParams) (*Report, error)

Subscriptions runs the push subscriptions report (POST /api/v1/reports/subscriptions/).

func (*ReportsService) Users

func (s *ReportsService) Users(ctx context.Context, params UsersReportParams) (*Report, error)

Users runs the users report (POST /api/v1/reports/users/).

type SignupParams

type SignupParams struct {
	// Email is the new user's email address. Also the login username.
	Email string

	FirstName string
	LastName  string

	// PhoneNumber is in E.164 format (e.g. "+96512345678").
	PhoneNumber string

	// Password for the new account (write-only).
	Password string

	// CivilID is the Kuwait Civil ID; validated and must be unique when
	// present.
	CivilID *string

	// DefaultLanguage is the two-letter code (e.g. "en", "ar").
	DefaultLanguage *string

	// ClientID is an optional client id for the auto-created contact
	// profile; defaults to "KMS<id>" server-side when omitted.
	ClientID *string
}

SignupParams are the parameters for AuthService.Signup. Email, FirstName, LastName, PhoneNumber and Password are required; nil fields are omitted from the request JSON.

type SkippedBreakdown

type SkippedBreakdown struct {
	// Suppressed counts recipients on the do-not-contact list — see
	// SuppressionsService. Fan-outs skip suppressed recipients instead of
	// erroring.
	Suppressed int `json:"suppressed"`

	// WrongChannel counts audience members not reachable on the
	// broadcast's channel.
	WrongChannel int `json:"wrong_channel"`

	// Duplicate counts duplicate addresses deduped out of an inline
	// recipients list.
	Duplicate int `json:"duplicate"`
}

SkippedBreakdown itemises why fan-out recipients were skipped, on the broadcast and batch create envelopes. Keys are always present in the JSON (0 when nothing was skipped in that bucket); the envelope's SkippedCount is their sum.

type SpecificBulksReportParams

type SpecificBulksReportParams struct {
	// BulksFile is required: the bulk batch id to report on.
	BulksFile int

	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string

	// MobileApp filters by push application slug.
	MobileApp []string

	// WebApp filters by web push widget slug.
	WebApp []string

	// Status filters by delivery status.
	Status []string
}

SpecificBulksReportParams are the parameters for ReportsService.SpecificBulks. Only BulksFile is required; nil fields are omitted from the request JSON.

type SubscriptionsReportParams

type SubscriptionsReportParams struct {
	// ReportType is required, e.g. "mobile" or "web".
	ReportType string

	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string

	// MobileApp filters by push application slug.
	MobileApp []string

	// WebApp filters by web push widget slug.
	WebApp []string

	// DeviceType filters by device platform.
	DeviceType []string
}

SubscriptionsReportParams are the parameters for ReportsService.Subscriptions. Only ReportType is required; nil fields are omitted from the request JSON.

type Suppression

type Suppression struct {
	// ID is the opaque suppression id, "sup_" prefixed.
	ID string `json:"id"`

	// Object is always the string "suppression".
	Object string `json:"object,omitempty"`

	// Address is the suppressed address, stored normalized (compact E.164
	// phone / lowercase email), so any formatting of the same address
	// matches.
	Address string `json:"address"`

	// Channel is the channel the suppression is scoped to (e.g. "sms");
	// nil means the address is suppressed on ALL channels.
	Channel *string `json:"channel,omitempty"`

	// Reason is why the address is suppressed: "manual", "unsubscribe",
	// "hard_bounce", or "stop".
	Reason string `json:"reason"`

	// Livemode is false when the row was created by an sk_test_ key —
	// test suppressions gate test sends only, live ones live sends.
	Livemode bool `json:"livemode"`

	// Created is when the suppression was created.
	Created *time.Time `json:"created,omitempty"`
}

Suppression is one do-not-contact row as returned by the API.

type SuppressionCreateParams

type SuppressionCreateParams struct {
	// Address is required: an E.164 phone number ("+96550001234" —
	// separators tolerated) or an email address. Stored normalized
	// (compact E.164 / lowercase).
	Address string

	// Channel scopes the suppression to one channel (e.g. "sms",
	// "whatsapp", "email"). Nil suppresses the address on ALL channels.
	Channel *string

	// Reason is why the address is suppressed — one of the
	// SuppressionReason* constants. Nil lets the server default to
	// "manual".
	Reason *string
}

SuppressionCreateParams are the parameters for SuppressionsService.Create. Nil fields are omitted from the JSON.

type SuppressionListParams

type SuppressionListParams struct {
	// Address filters to one address. It is normalized before matching,
	// so a formatted phone finds its compact row.
	Address *string

	// Channel filters to suppressions scoped to one channel (e.g. "sms").
	// All-channel rows (Channel == nil) are not matched by this filter.
	Channel *string

	// Reason filters by suppression reason — one of the
	// SuppressionReason* constants.
	Reason *string

	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

SuppressionListParams filter and paginate SuppressionsService.List. Nil fields are omitted from the query.

type SuppressionsService

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

SuppressionsService manages the workspace's do-not-contact list (/api/v1/suppressions/). Rows are enforced mode-scoped on EVERY send path, matching on (address, channel) or (address, all channels): single-recipient sends (MessagesService.Send with To, OTPService.Send) to a suppressed address are rejected with a 422 slug "recipient-suppressed", while fan-outs (broadcasts, batches, bulk) skip suppressed recipients into the envelope's Skipped.Suppressed counter instead. Access it via Client.Suppressions.

func (*SuppressionsService) Create

Create adds an address to the do-not-contact list (POST /api/v1/suppressions/, 201). From that moment the address is enforced across every send surface in the key's mode.

Create is IDEMPOTENT BY NATURE: creating a duplicate (Address, Channel) in the same mode answers 200 with the EXISTING suppression — never an error — so no Idempotency-Key header is sent. Requires the suppressions:write scope.

func (*SuppressionsService) Delete

func (s *SuppressionsService) Delete(ctx context.Context, suppressionID string) error

Delete removes a suppression, making the address contactable again (DELETE /api/v1/suppressions/{suppression_id}/ — trailing slash, 204 on success). An unknown or mode-mismatched id is a 404. Requires the suppressions:write scope.

func (*SuppressionsService) List

List pages through the workspace's suppression list, newest first (GET /api/v1/suppressions/ — trailing slash; cursor-paginated), optionally filtered by Address, Channel, or Reason. Only rows in the key's mode are returned — test keys see test suppressions, live keys live ones. Requires the suppressions:read scope.

type Template

type Template struct {
	// Slug is the unique template identifier — the value sends reference
	// via template: {"slug": ...}.
	Slug string `json:"slug"`

	// Object is always the string "template".
	Object string `json:"object,omitempty"`

	// Channel is an optional channel hint (metadata only — it never
	// restricts which channel may render the template); nil for a
	// cross-channel template.
	Channel *string `json:"channel,omitempty"`

	// Subject is the subject line (used by email; notification title on
	// push).
	Subject string `json:"subject"`

	// Version is the latest version number (immutable ints starting at 1).
	Version int `json:"version"`

	// Created is when the template was created.
	Created *time.Time `json:"created,omitempty"`

	// Updated is when the template last changed.
	Updated *time.Time `json:"updated,omitempty"`

	// Body is the latest HTML body — detail endpoints only (empty on list
	// rows).
	Body string `json:"body,omitempty"`

	// BodyMd is the latest markdown body — detail endpoints only (empty on
	// list rows).
	BodyMd string `json:"body_md,omitempty"`

	// Versions are all available version numbers, ascending — detail
	// endpoints only (nil on list rows). Pin one on a send via
	// template: {"slug": ..., "version": N}.
	Versions []int `json:"versions,omitempty"`
}

Template is a stored message template. The list rows (List) carry the metadata fields; the detail endpoints (Create, Retrieve, Update) ADD Body, BodyMd and Versions.

type TemplateCreateParams

type TemplateCreateParams struct {
	// Slug is required and unique: lowercase letters, digits, hyphens and
	// underscores. It is the value sends reference via template: {"slug":
	// ...}. A duplicate slug (including an archived one) is a 409 slug
	// "template-exists".
	Slug string

	// Channel is an optional channel hint (metadata only — never restricts
	// rendering). Nil omits it for a cross-channel template.
	Channel *string

	// Subject is the subject line (email / push title). Nil lets the
	// server default to "".
	Subject *string

	// Body is the HTML body. Nil lets the server default to "".
	Body *string

	// BodyMd is the markdown body. Nil lets the server default to "".
	BodyMd *string
}

TemplateCreateParams are the parameters for TemplatesService.Create. Nil fields are omitted from the JSON.

type TemplateListParams

type TemplateListParams struct {
	// Channel filters to templates carrying this channel hint (e.g. "sms").
	Channel *string

	// Q is a slug-prefix search — "order" matches "order-shipped",
	// "order-refund", ...
	Q *string

	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

TemplateListParams filter and paginate TemplatesService.List. Nil fields are omitted from the query.

type TemplateUpdateParams

type TemplateUpdateParams struct {
	// Channel is the new channel hint (metadata — no version bump).
	Channel *string

	// Subject is the new subject line (content — mints a new version when
	// changed).
	Subject *string

	// Body is the new HTML body (content — mints a new version when
	// changed).
	Body *string

	// BodyMd is the new markdown body (content — mints a new version when
	// changed).
	BodyMd *string
}

TemplateUpdateParams are the parameters for TemplatesService.Update. Only non-nil fields are sent; the server requires at least one. Changing any CONTENT field (Subject / Body / BodyMd) mints an immutable version N+1; Channel is metadata and never bumps the version, and a no-op content PATCH mints nothing.

type TemplatesService

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

TemplatesService manages slug-keyed message templates with an IMMUTABLE version spine — the same rows a send renders for template: {"slug": ...} (see MessageSendParams.Template). Changing a content field (Subject / Body / BodyMd) mints an immutable version N+1; a send pins an older revision with template: {"slug": ..., "version": N}. Access it via Client.Templates.

func (*TemplatesService) Create

Create adds a template at version 1 (POST /api/v1/templates/, 201). A duplicate slug (including a previously archived one) is a 409 slug "template-exists". Requires the templates:write scope.

func (*TemplatesService) Delete

func (s *TemplatesService) Delete(ctx context.Context, slug string) error

Delete soft-archives a template (DELETE /api/v1/templates/{slug}/, 204 on success). Archived templates read as MISSING everywhere and sends referencing the slug fail resolution, but history and delivery-log FKs survive and the slug stays reserved (re-create is a 409 slug "template-exists"). An unknown or already-archived slug is a 404 slug "template-not-found". Requires the templates:write scope.

func (*TemplatesService) List

List pages through the workspace's templates (GET /api/v1/templates/ — cursor-paginated), optionally filtered by Channel or a slug-prefix Q. Requires the templates:read scope.

func (*TemplatesService) Retrieve

func (s *TemplatesService) Retrieve(ctx context.Context, slug string) (*Template, error)

Retrieve fetches one template with its latest content and version list (GET /api/v1/templates/{slug}/). An unknown or archived slug is a 404 slug "template-not-found". Requires the templates:read scope.

func (*TemplatesService) Update

func (s *TemplatesService) Update(ctx context.Context, slug string, params TemplateUpdateParams) (*Template, error)

Update partially updates a template (PATCH /api/v1/templates/{slug}/). The server requires at least one of Channel, Subject, Body or BodyMd; changing a content field mints a new immutable version. An unknown or archived slug is a 404 slug "template-not-found". Requires the templates:write scope.

type UserProfile

type UserProfile struct {
	// Email is the user's email address. Also the login username.
	Email string `json:"email"`

	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`

	// PhoneNumber is in E.164 format (e.g. "+96512345678").
	PhoneNumber string `json:"phone_number"`

	// CivilID is the Kuwait Civil ID; nullable.
	CivilID *string `json:"civil_id,omitempty"`

	// DefaultLanguage is the two-letter code (e.g. "en", "ar").
	DefaultLanguage string `json:"default_language,omitempty"`

	// ClientID is the linked contact profile's client_id (read-only).
	ClientID string `json:"client_id,omitempty"`
}

UserProfile is the authenticated user's own profile — the body of GET /api/v1/profile/ and the created profile echoed by POST /api/v1/signup/.

type UsersReportParams

type UsersReportParams struct {
	// DateFrom / DateTo bound the report window ("YYYY-MM-DD").
	DateFrom *string
	DateTo   *string

	// Search filters rows by free text.
	Search *string
}

UsersReportParams are the parameters for ReportsService.Users. All fields are optional; nil fields are omitted from the request JSON.

type WABA

type WABA struct {
	ID   *int    `json:"id,omitempty"`
	Name *string `json:"name,omitempty"`
}

WABA identifies the WhatsApp Business Account a template belongs to.

type WebPushSubscription

type WebPushSubscription struct {
	ClientID         string `json:"client_id"`
	Slug             string `json:"slug"`
	SubscriptionInfo string `json:"subscription_info,omitempty"`
}

WebPushSubscription is the echo body of the legacy POST /api/v1/webpush/client/.

type WebhookAttempt

type WebhookAttempt struct {
	// ID is the opaque attempt id, "wha_" prefixed.
	ID string `json:"id"`

	// Object is always the string "webhook_attempt".
	Object string `json:"object,omitempty"`

	// EventID is the id of the delivered event ("evt_" prefixed) — fetch
	// it via EventsService.Retrieve.
	EventID string `json:"event_id"`

	// EventType is the type of the delivered event, e.g.
	// "message.delivered".
	EventType string `json:"event_type"`

	// Attempts is how many delivery attempts have been made so far.
	Attempts int `json:"attempts"`

	// ResponseStatus is the HTTP status of the most recent attempt; nil
	// when the endpoint never answered (timeout / connection failure).
	ResponseStatus *int `json:"response_status,omitempty"`

	// OK is true once an attempt got a 2xx answer (delivery succeeded).
	OK bool `json:"ok"`

	// Error is the failure reason of the most recent attempt ("HTTP
	// <status>" or a transport error); nil when the delivery succeeded.
	Error *string `json:"error,omitempty"`

	// LastAttemptAt is when the most recent attempt ran; nil before the
	// first attempt.
	LastAttemptAt *time.Time `json:"last_attempt_at,omitempty"`

	// NextAttemptAt is when the next retry is scheduled; nil when the
	// delivery is settled (succeeded, or retries exhausted).
	NextAttemptAt *time.Time `json:"next_attempt_at,omitempty"`

	// Created is when the delivery was first enqueued.
	Created *time.Time `json:"created,omitempty"`
}

WebhookAttempt is one (event, endpoint) delivery ledger row returned by WebhookEndpointsService.ListAttempts.

type WebhookAttemptListParams

type WebhookAttemptListParams struct {
	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

WebhookAttemptListParams are the optional cursor-pagination parameters for WebhookEndpointsService.ListAttempts. Nil fields are omitted from the query.

type WebhookEndpoint

type WebhookEndpoint struct {
	// ID is the opaque endpoint id, "we_" prefixed.
	ID string `json:"id"`

	// Object is always the string "webhook_endpoint".
	Object string `json:"object,omitempty"`

	// URL is the HTTPS URL that event envelopes are POSTed to.
	URL string `json:"url"`

	// Description is a free-text label for the endpoint.
	Description string `json:"description,omitempty"`

	// EnabledEvents are the event types delivered to this endpoint, or
	// ["*"] for all.
	EnabledEvents []string `json:"enabled_events,omitempty"`

	// Livemode is the endpoint's mode routing, fixed at create time.
	// true: receives events from live sends only; false: receives events
	// from test-mode (sk_test_) sends only.
	Livemode bool `json:"livemode"`

	// Status is "enabled" (receiving deliveries) or "disabled".
	Status string `json:"status,omitempty"`

	// CreatedAt is when the endpoint was created.
	CreatedAt *time.Time `json:"created_at,omitempty"`
}

WebhookEndpoint is a webhook endpoint as returned by the API.

The signing secret is never present here — it is revealed once, in the create response (WebhookEndpointWithSecret).

type WebhookEndpointCreateParams

type WebhookEndpointCreateParams struct {
	// URL is required: the HTTPS URL signed event envelopes are POSTed to.
	URL string

	// Description is an optional free-text label.
	Description *string

	// EnabledEvents are the event types to deliver. Nil lets the server
	// default to ["*"] (everything).
	EnabledEvents []string

	// Livemode fixes the endpoint's mode routing at create time. Nil lets
	// the server default to true (live events only); silon.Bool(false)
	// subscribes the endpoint to test-mode (sk_test_) events only.
	Livemode *bool
}

WebhookEndpointCreateParams are the parameters for WebhookEndpointsService.Create. Nil fields are omitted from the JSON.

type WebhookEndpointListParams

type WebhookEndpointListParams struct {
	// Cursor resumes listing from an opaque pagination cursor.
	Cursor *string

	// Limit caps the page size.
	Limit *int
}

WebhookEndpointListParams are the optional cursor-pagination parameters for WebhookEndpointsService.List. Nil fields are omitted from the query.

type WebhookEndpointTestResult

type WebhookEndpointTestResult struct {
	// Delivered is true when the endpoint answered with a 2xx status.
	Delivered bool `json:"delivered"`

	// ResponseStatus is the HTTP status the endpoint answered with; nil
	// when no response arrived (timeout, connection refused, DNS failure).
	ResponseStatus *int `json:"response_status,omitempty"`

	// LatencyMs is the round-trip time of the ping in milliseconds.
	LatencyMs int `json:"latency_ms"`

	// Error is nil on success; otherwise "HTTP <status>" for a non-2xx
	// answer, or the transport failure (e.g. a timeout message).
	Error *string `json:"error,omitempty"`
}

WebhookEndpointTestResult is the result of a synchronous test ping (WebhookEndpointsService.Test). A failing sink is NOT an HTTP error — the call returns a result with Delivered false and the reason in Error.

type WebhookEndpointUpdateParams

type WebhookEndpointUpdateParams struct {
	// URL is the new HTTPS delivery URL.
	URL *string

	// Description is the new free-text label.
	Description *string

	// EnabledEvents replaces the delivered event types (["*"] for all).
	EnabledEvents []string

	// Status set to "disabled" pauses deliveries without deleting the
	// endpoint; "enabled" resumes them.
	Status *string
}

WebhookEndpointUpdateParams are the parameters for WebhookEndpointsService.Update. Only non-nil fields are sent.

type WebhookEndpointWithSecret

type WebhookEndpointWithSecret struct {
	WebhookEndpoint

	// Secret is the signing secret ("whsec_" prefix). Shown ONCE, only
	// here — store it now; it is never returned again. Use it to verify
	// the Silon-Signature header on every delivery.
	Secret string `json:"secret"`
}

WebhookEndpointWithSecret is the create response — a WebhookEndpoint plus the one-time signing secret.

type WebhookEndpointsService

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

WebhookEndpointsService manages outbound webhook subscriptions (/api/v1/webhook_endpoints). Access it via Client.WebhookEndpoints.

func (*WebhookEndpointsService) Create

Create subscribes an HTTPS URL to events (POST /api/v1/webhook_endpoints).

The response includes the one-time signing secret ("whsec_" prefix) — store it now, it is never returned again. Set params.Livemode to silon.Bool(false) to receive test-mode (sk_test_) events instead of live ones — the mode is fixed at create time.

func (*WebhookEndpointsService) Delete

func (s *WebhookEndpointsService) Delete(ctx context.Context, endpointID string) error

Delete removes a webhook endpoint (DELETE /api/v1/webhook_endpoints/{endpoint_id} — no trailing slash, 204 on success).

func (*WebhookEndpointsService) List

List pages through webhook endpoints (GET /api/v1/webhook_endpoints — no trailing slash; cursor-paginated).

func (*WebhookEndpointsService) ListAttempts

func (s *WebhookEndpointsService) ListAttempts(ctx context.Context, endpointID string, params WebhookAttemptListParams) (*Page[WebhookAttempt], error)

ListAttempts pages through the (event, endpoint) delivery ledger for one endpoint, newest first (GET /api/v1/webhook_endpoints/{endpoint_id}/ attempts — no trailing slash; cursor-paginated). An unknown endpoint id is a 404 slug "resource-not-found". Requires the webhooks:read scope.

func (*WebhookEndpointsService) Retrieve

func (s *WebhookEndpointsService) Retrieve(ctx context.Context, endpointID string) (*WebhookEndpoint, error)

Retrieve fetches one webhook endpoint by id (GET /api/v1/webhook_endpoints/{endpoint_id} — no trailing slash).

func (*WebhookEndpointsService) Test

Test synchronously POSTs a signed "ping" envelope to the endpoint URL and returns the delivery result (POST /api/v1/webhook_endpoints/ {endpoint_id}/test — no trailing slash, no request body).

A failing sink is NOT an HTTP error: the call succeeds with result.Delivered false and the reason in result.Error. The endpoint id must match the key's mode (a live key tests livemode-true endpoints, a test key livemode-false ones); a mode mismatch or unknown id is a 404 slug "resource-not-found". Test pings are never persisted and never appear in ListAttempts. Requires the webhooks:write scope.

func (*WebhookEndpointsService) Update

Update partially updates a webhook endpoint (PATCH /api/v1/webhook_endpoints/{endpoint_id} — no trailing slash). Set Status to "disabled" to pause deliveries.

type WebhookSignatureVerificationError

type WebhookSignatureVerificationError struct {
	Message string
}

WebhookSignatureVerificationError is returned by ConstructWebhookEvent when a payload fails Silon-Signature verification.

func (*WebhookSignatureVerificationError) Error

type WhatsAppTemplate

type WhatsAppTemplate struct {
	// ID is the local template id (the path param on the detail
	// endpoint).
	ID int `json:"id"`

	// Name is the template name — pass it as whatsapp_template.name
	// when sending a Meta-Cloud template.
	Name string `json:"name"`

	// Language is the template language code, e.g. "en".
	Language string `json:"language,omitempty"`

	// Category is MARKETING / UTILITY / AUTHENTICATION.
	Category string `json:"category,omitempty"`

	// Status is the Meta-mirrored status (always APPROVED here).
	Status string `json:"status,omitempty"`

	// ExternalID is the Meta template ID (blank until submitted).
	ExternalID string `json:"external_id,omitempty"`

	// WABA is the WhatsApp Business Account the template belongs to.
	WABA WABA `json:"waba"`

	// Preview is the rendered preview text of the template body.
	Preview string `json:"preview,omitempty"`

	// Mode is "auth" or "structured".
	Mode string `json:"mode,omitempty"`

	// Variables are the variable slots this template expects.
	Variables []WhatsAppTemplateVariable `json:"variables,omitempty"`

	// Components is the raw Meta-shaped component payload — detail
	// endpoint only; nil on list rows.
	Components []map[string]any `json:"components,omitempty"`
}

WhatsAppTemplate is a single approved WhatsApp template.

Components (the raw Meta-shaped component payload) is only present on the detail endpoint (WhatsAppTemplatesService.Retrieve); the list endpoint omits it.

type WhatsAppTemplateList

type WhatsAppTemplateList struct {
	// Count is the number of matching templates.
	Count int `json:"count"`

	// Results are the approved templates on this page.
	Results []WhatsAppTemplate `json:"results"`
}

WhatsAppTemplateList is the envelope returned by GET /api/v1/whatsapp/templates/.

type WhatsAppTemplateListParams

type WhatsAppTemplateListParams struct {
	// Name filters by template name.
	Name *string

	// Language filters by language code, e.g. "en".
	Language *string

	// WABAID filters to one WhatsApp Business Account.
	WABAID *int
}

WhatsAppTemplateListParams filter WhatsAppTemplatesService.List. Nil fields are omitted from the query.

type WhatsAppTemplateVariable

type WhatsAppTemplateVariable struct {
	// Key is the exact name to use in the send whatsapp_template
	// variables map (e.g. "body_1", "header_1", "otp_code").
	Key string `json:"key"`

	// Label is a human-readable label for the variable (e.g. "{{1}}").
	Label string `json:"label,omitempty"`
}

WhatsAppTemplateVariable is one variable a caller must supply when sending the template.

type WhatsAppTemplatesService

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

WhatsAppTemplatesService lists approved WhatsApp templates (/api/v1/whatsapp/templates/). Access it via Client.WhatsAppTemplates.

func (*WhatsAppTemplatesService) List

List fetches approved WhatsApp templates, optionally filtered by name / language / WABA (GET /api/v1/whatsapp/templates/).

func (*WhatsAppTemplatesService) Retrieve

func (s *WhatsAppTemplatesService) Retrieve(ctx context.Context, templateID int) (*WhatsAppTemplate, error)

Retrieve fetches one template with its components and variables (GET /api/v1/whatsapp/templates/{id}/).

Jump to

Keyboard shortcuts

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