Documentation
¶
Overview ¶
Package whatsapp implements the WhatsApp Business Cloud API adapter for the orb chat gateway: webhook ingress (hub handshake + HMAC-signed events), final-message delivery with reply threading, and authenticated media download.
The adapter speaks only the official Graph Cloud API with caller-supplied credentials. Unsigned webhooks are refused by construction: New errors without an AppSecret, matching the Hermes reference stance. The Cloud API has no message editing, so Preview is a no-op and only the final reply is sent (no fake streaming).
Index ¶
- Constants
- func ChunkText(text string, limit int) []string
- func FormatText(markdown string) string
- func StatusRank(status string) int
- type Adapter
- func (a *Adapter) Account() string
- func (a *Adapter) Download(ctx context.Context, ref chat.AttachmentRef) (io.ReadCloser, string, error)
- func (a *Adapter) NewDelivery(key chat.ConversationKey, replyTo, resumePreviewID string) chat.Delivery
- func (a *Adapter) Platform() string
- func (a *Adapter) Webhook(publish func(chat.Message) error) http.Handler
- type GraphError
- type Options
- type Status
Constants ¶
const GraphVersion = "v23.0"
GraphVersion is the pinned Graph API version used for every call.
Variables ¶
This section is empty.
Functions ¶
func ChunkText ¶
ChunkText splits text into chunks of at most limit characters (runes), preferring paragraph breaks, then line breaks, then spaces, then a hard cut. Empty input yields no chunks.
ponytail: chunk boundaries ignore code fences — a >4096-char fence splits mid-block; accepted ceiling.
func FormatText ¶
FormatText converts common markdown to WhatsApp markup: **b**/__b__ → *b*, ~~s~~ → ~s~, headings → *bold* lines, [text](url) → "text (url)". Fenced code blocks are kept as triple-backtick blocks (WhatsApp renders them as monospace); the language token on the opening fence is dropped because WhatsApp would display it literally.
ponytail: line/regex transform, not a goldmark AST walk; single-asterisk and single-underscore emphasis pass through unchanged (md italic renders as WhatsApp bold/italic respectively — accepted ceiling).
func StatusRank ¶
StatusRank orders status values so out-of-order callbacks can be reduced to the furthest-progressed state: read > delivered > sent, with the terminal "failed" above all and unknown values at 0.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter is a chat.Adapter for the WhatsApp Business Cloud API.
func New ¶
New builds the adapter. It refuses to construct without Token, PhoneNumberID, AppSecret, and VerifyToken.
func (*Adapter) Account ¶
Account returns the business phone number id this adapter serves, matching the Account set on every normalized message.
func (*Adapter) Download ¶
func (a *Adapter) Download(ctx context.Context, ref chat.AttachmentRef) (io.ReadCloser, string, error)
Download implements chat.Adapter: media id → metadata (url) → authenticated download. Media URLs expire after ~5 minutes, so a failed download refetches the metadata once for a fresh URL before giving up.
func (*Adapter) NewDelivery ¶
func (a *Adapter) NewDelivery(key chat.ConversationKey, replyTo, resumePreviewID string) chat.Delivery
NewDelivery implements chat.Adapter. resumePreviewID is ignored: the Cloud API cannot edit messages, so this adapter never creates previews and crash recovery simply resends the final text.
func (*Adapter) Webhook ¶
Webhook returns the HTTP handler for the Cloud API webhook endpoint. GET answers the one-time subscribe handshake; POST verifies the X-Hub-Signature-256 HMAC over the raw body BEFORE parsing, publishes every normalized message, and forwards statuses[] to Options.OnStatus. A publish error yields a 500 so Meta redelivers (the wamid EventID dedupes downstream).
type GraphError ¶
type GraphError struct {
Code int `json:"code"`
Subcode int `json:"error_subcode,omitempty"`
Type string `json:"type,omitempty"`
Title string `json:"title,omitempty"`
Message string `json:"message,omitempty"`
ErrorData struct {
Details string `json:"details,omitempty"`
} `json:"error_data,omitempty"`
FBTraceID string `json:"fbtrace_id,omitempty"`
}
GraphError is one Graph API error object, either from an HTTP error response envelope or from a statuses[].errors[] entry.
type Options ¶
type Options struct {
// Token is the Graph API access token (Bearer). Required.
Token string
// PhoneNumberID is the business phone number id calls are made on
// behalf of. Required.
PhoneNumberID string
// AppSecret signs inbound webhooks (X-Hub-Signature-256). Required:
// unsigned webhooks are not an option.
AppSecret string
// VerifyToken answers the one-time GET subscribe handshake. Required.
VerifyToken string
// BaseURL overrides the Graph endpoint (tests). Default
// https://graph.facebook.com.
BaseURL string
// HTTPClient overrides the HTTP client. Default: 30s timeout.
HTTPClient *http.Client
// OnStatus receives delivery status callbacks from the statuses[]
// webhook array. Default: no-op. Statuses arrive out of order; keep the
// highest [StatusRank] per message id.
OnStatus func(Status)
}
Options configures the WhatsApp Cloud API adapter.
type Status ¶
type Status struct {
// MessageID is the wamid of the outbound message the status refers to.
MessageID string `json:"id"`
// Status is "sent", "delivered", "read", or "failed".
Status string `json:"status"`
// Timestamp is epoch seconds, as a string (all WhatsApp timestamps are).
Timestamp string `json:"timestamp"`
// RecipientID is the recipient wa_id.
RecipientID string `json:"recipient_id"`
// Errors is populated on "failed" statuses.
Errors []GraphError `json:"errors,omitempty"`
}
Status is one entry of the statuses[] webhook array. The lifecycle per message id is sent → delivered → read (or the terminal failed), but callbacks arrive out of order: consumers must keep the highest StatusRank seen per MessageID instead of trusting arrival order.