feat(whatsapp): ✨ Enhance webhook message handling
* Add support for new message types: audio, sticker, location, contacts, interactive, button, reaction, order, system, and unknown. * Implement logging for various webhook events for better visibility. * Update WebhookMessage struct to include new fields for enhanced message processing.
This commit is contained in:
@@ -90,20 +90,26 @@ func (m *Manager) Start() {
|
|||||||
for _, eventType := range allEventTypes {
|
for _, eventType := range allEventTypes {
|
||||||
m.eventBus.Subscribe(eventType, m.handleEvent)
|
m.eventBus.Subscribe(eventType, m.handleEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging.Info("Hook manager started and subscribed to events", "event_types", len(allEventTypes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleEvent processes any event and triggers relevant hooks
|
// handleEvent processes any event and triggers relevant hooks
|
||||||
func (m *Manager) handleEvent(event events.Event) {
|
func (m *Manager) handleEvent(event events.Event) {
|
||||||
|
logging.Debug("Hook manager received event", "event_type", event.Type)
|
||||||
|
|
||||||
// Get hooks that are subscribed to this event type
|
// Get hooks that are subscribed to this event type
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
relevantHooks := make([]config.Hook, 0)
|
relevantHooks := make([]config.Hook, 0)
|
||||||
for _, hook := range m.hooks {
|
for _, hook := range m.hooks {
|
||||||
if !hook.Active {
|
if !hook.Active {
|
||||||
|
logging.Debug("Skipping inactive hook", "hook_id", hook.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If hook has no events specified, subscribe to all events
|
// If hook has no events specified, subscribe to all events
|
||||||
if len(hook.Events) == 0 {
|
if len(hook.Events) == 0 {
|
||||||
|
logging.Debug("Hook subscribes to all events", "hook_id", hook.ID)
|
||||||
relevantHooks = append(relevantHooks, hook)
|
relevantHooks = append(relevantHooks, hook)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -112,6 +118,7 @@ func (m *Manager) handleEvent(event events.Event) {
|
|||||||
eventTypeStr := string(event.Type)
|
eventTypeStr := string(event.Type)
|
||||||
for _, subscribedEvent := range hook.Events {
|
for _, subscribedEvent := range hook.Events {
|
||||||
if subscribedEvent == eventTypeStr {
|
if subscribedEvent == eventTypeStr {
|
||||||
|
logging.Debug("Hook matches event", "hook_id", hook.ID, "event_type", eventTypeStr)
|
||||||
relevantHooks = append(relevantHooks, hook)
|
relevantHooks = append(relevantHooks, hook)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -119,6 +126,8 @@ func (m *Manager) handleEvent(event events.Event) {
|
|||||||
}
|
}
|
||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
logging.Debug("Found relevant hooks for event", "event_type", event.Type, "hook_count", len(relevantHooks))
|
||||||
|
|
||||||
// Trigger each relevant hook
|
// Trigger each relevant hook
|
||||||
if len(relevantHooks) > 0 {
|
if len(relevantHooks) > 0 {
|
||||||
m.triggerHooksForEvent(event, relevantHooks)
|
m.triggerHooksForEvent(event, relevantHooks)
|
||||||
@@ -265,17 +274,24 @@ func (m *Manager) ListHooks() []config.Hook {
|
|||||||
|
|
||||||
// sendToHook sends any payload to a specific hook with explicit event type
|
// sendToHook sends any payload to a specific hook with explicit event type
|
||||||
func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload interface{}, eventType events.EventType) *HookResponse {
|
func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload interface{}, eventType events.EventType) *HookResponse {
|
||||||
if ctx == nil {
|
// Create a new context detached from the incoming context to prevent cancellation
|
||||||
ctx = context.Background()
|
// when the original HTTP request completes. Use a 30-second timeout to match client timeout.
|
||||||
|
hookCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Use the original context for event publishing (if available)
|
||||||
|
eventCtx := ctx
|
||||||
|
if eventCtx == nil {
|
||||||
|
eventCtx = context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish hook triggered event
|
// Publish hook triggered event
|
||||||
m.eventBus.Publish(events.HookTriggeredEvent(ctx, hook.ID, hook.Name, hook.URL, payload))
|
m.eventBus.Publish(events.HookTriggeredEvent(eventCtx, hook.ID, hook.Name, hook.URL, payload))
|
||||||
|
|
||||||
data, err := json.Marshal(payload)
|
data, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to marshal payload", "hook_id", hook.ID, "error", err)
|
logging.Error("Failed to marshal payload", "hook_id", hook.ID, "error", err)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, err))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +299,7 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
|||||||
parsedURL, err := url.Parse(hook.URL)
|
parsedURL, err := url.Parse(hook.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to parse hook URL", "hook_id", hook.ID, "error", err)
|
logging.Error("Failed to parse hook URL", "hook_id", hook.ID, "error", err)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, err))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,10 +327,10 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
|||||||
}
|
}
|
||||||
parsedURL.RawQuery = query.Encode()
|
parsedURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, hook.Method, parsedURL.String(), bytes.NewReader(data))
|
req, err := http.NewRequestWithContext(hookCtx, hook.Method, parsedURL.String(), bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to create request", "hook_id", hook.ID, "error", err)
|
logging.Error("Failed to create request", "hook_id", hook.ID, "error", err)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, err))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,14 +344,14 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
|||||||
resp, err := m.client.Do(req)
|
resp, err := m.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to send to hook", "hook_id", hook.ID, "error", err)
|
logging.Error("Failed to send to hook", "hook_id", hook.ID, "error", err)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, err))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
logging.Warn("Hook returned non-success status", "hook_id", hook.ID, "status", resp.StatusCode)
|
logging.Warn("Hook returned non-success status", "hook_id", hook.ID, "status", resp.StatusCode)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, fmt.Errorf("status code %d", resp.StatusCode)))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, fmt.Errorf("status code %d", resp.StatusCode)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,23 +359,23 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
|||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Error("Failed to read hook response", "hook_id", hook.ID, "error", err)
|
logging.Error("Failed to read hook response", "hook_id", hook.ID, "error", err)
|
||||||
m.eventBus.Publish(events.HookFailedEvent(ctx, hook.ID, hook.Name, err))
|
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
m.eventBus.Publish(events.HookSuccessEvent(ctx, hook.ID, hook.Name, resp.StatusCode, nil))
|
m.eventBus.Publish(events.HookSuccessEvent(eventCtx, hook.ID, hook.Name, resp.StatusCode, nil))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var hookResp HookResponse
|
var hookResp HookResponse
|
||||||
if err := json.Unmarshal(body, &hookResp); err != nil {
|
if err := json.Unmarshal(body, &hookResp); err != nil {
|
||||||
logging.Debug("Hook response not JSON", "hook_id", hook.ID)
|
logging.Debug("Hook response not JSON", "hook_id", hook.ID)
|
||||||
m.eventBus.Publish(events.HookSuccessEvent(ctx, hook.ID, hook.Name, resp.StatusCode, string(body)))
|
m.eventBus.Publish(events.HookSuccessEvent(eventCtx, hook.ID, hook.Name, resp.StatusCode, string(body)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.Debug("Hook response received", "hook_id", hook.ID, "send_message", hookResp.SendMessage)
|
logging.Debug("Hook response received", "hook_id", hook.ID, "send_message", hookResp.SendMessage)
|
||||||
m.eventBus.Publish(events.HookSuccessEvent(ctx, hook.ID, hook.Name, resp.StatusCode, hookResp))
|
m.eventBus.Publish(events.HookSuccessEvent(eventCtx, hook.ID, hook.Name, resp.StatusCode, hookResp))
|
||||||
return &hookResp
|
return &hookResp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.warky.dev/wdevs/whatshooked/pkg/events"
|
"git.warky.dev/wdevs/whatshooked/pkg/events"
|
||||||
@@ -44,14 +45,53 @@ func (c *Client) HandleWebhook(r *http.Request) error {
|
|||||||
func (c *Client) processChange(change WebhookChange) {
|
func (c *Client) processChange(change WebhookChange) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Process messages
|
// Handle different field types
|
||||||
for _, msg := range change.Value.Messages {
|
switch change.Field {
|
||||||
c.processMessage(ctx, msg, change.Value.Contacts)
|
case "messages":
|
||||||
}
|
// Process messages
|
||||||
|
for _, msg := range change.Value.Messages {
|
||||||
|
c.processMessage(ctx, msg, change.Value.Contacts)
|
||||||
|
}
|
||||||
|
|
||||||
// Process statuses
|
// Process statuses
|
||||||
for _, status := range change.Value.Statuses {
|
for _, status := range change.Value.Statuses {
|
||||||
c.processStatus(ctx, status)
|
c.processStatus(ctx, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "message_template_status_update":
|
||||||
|
// Log template status updates for visibility
|
||||||
|
logging.Info("Message template status update received",
|
||||||
|
"account_id", c.id,
|
||||||
|
"phone_number_id", change.Value.Metadata.PhoneNumberID)
|
||||||
|
|
||||||
|
case "account_update":
|
||||||
|
// Log account updates
|
||||||
|
logging.Info("Account update received",
|
||||||
|
"account_id", c.id,
|
||||||
|
"phone_number_id", change.Value.Metadata.PhoneNumberID)
|
||||||
|
|
||||||
|
case "phone_number_quality_update":
|
||||||
|
// Log quality updates
|
||||||
|
logging.Info("Phone number quality update received",
|
||||||
|
"account_id", c.id,
|
||||||
|
"phone_number_id", change.Value.Metadata.PhoneNumberID)
|
||||||
|
|
||||||
|
case "phone_number_name_update":
|
||||||
|
// Log name updates
|
||||||
|
logging.Info("Phone number name update received",
|
||||||
|
"account_id", c.id,
|
||||||
|
"phone_number_id", change.Value.Metadata.PhoneNumberID)
|
||||||
|
|
||||||
|
case "account_alerts":
|
||||||
|
// Log account alerts
|
||||||
|
logging.Warn("Account alert received",
|
||||||
|
"account_id", c.id,
|
||||||
|
"phone_number_id", change.Value.Metadata.PhoneNumberID)
|
||||||
|
|
||||||
|
default:
|
||||||
|
logging.Debug("Unknown webhook field type",
|
||||||
|
"account_id", c.id,
|
||||||
|
"field", change.Field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +170,114 @@ func (c *Client) processMessage(ctx context.Context, msg WebhookMessage, contact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "audio":
|
||||||
|
if msg.Audio != nil {
|
||||||
|
messageType = "audio"
|
||||||
|
mimeType = msg.Audio.MimeType
|
||||||
|
|
||||||
|
// Download and process media
|
||||||
|
data, _, err := c.downloadMedia(ctx, msg.Audio.ID)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("Failed to download audio", "account_id", c.id, "media_id", msg.Audio.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
filename, mediaURL = c.processMediaData(msg.ID, data, mimeType, &mediaBase64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "sticker":
|
||||||
|
if msg.Sticker != nil {
|
||||||
|
messageType = "sticker"
|
||||||
|
mimeType = msg.Sticker.MimeType
|
||||||
|
|
||||||
|
// Download and process media
|
||||||
|
data, _, err := c.downloadMedia(ctx, msg.Sticker.ID)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("Failed to download sticker", "account_id", c.id, "media_id", msg.Sticker.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
filename, mediaURL = c.processMediaData(msg.ID, data, mimeType, &mediaBase64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "location":
|
||||||
|
if msg.Location != nil {
|
||||||
|
messageType = "location"
|
||||||
|
// Format location as text
|
||||||
|
text = fmt.Sprintf("Location: %s (%s) - %.6f, %.6f",
|
||||||
|
msg.Location.Name, msg.Location.Address,
|
||||||
|
msg.Location.Latitude, msg.Location.Longitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "contacts":
|
||||||
|
if len(msg.Contacts) > 0 {
|
||||||
|
messageType = "contacts"
|
||||||
|
// Format contacts as text
|
||||||
|
var contactNames []string
|
||||||
|
for _, contact := range msg.Contacts {
|
||||||
|
contactNames = append(contactNames, contact.Name.FormattedName)
|
||||||
|
}
|
||||||
|
text = fmt.Sprintf("Shared %d contact(s): %s", len(msg.Contacts), strings.Join(contactNames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
case "interactive":
|
||||||
|
if msg.Interactive != nil {
|
||||||
|
messageType = "interactive"
|
||||||
|
switch msg.Interactive.Type {
|
||||||
|
case "button_reply":
|
||||||
|
if msg.Interactive.ButtonReply != nil {
|
||||||
|
text = msg.Interactive.ButtonReply.Title
|
||||||
|
}
|
||||||
|
case "list_reply":
|
||||||
|
if msg.Interactive.ListReply != nil {
|
||||||
|
text = msg.Interactive.ListReply.Title
|
||||||
|
}
|
||||||
|
case "nfm_reply":
|
||||||
|
if msg.Interactive.NfmReply != nil {
|
||||||
|
text = msg.Interactive.NfmReply.Body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "button":
|
||||||
|
if msg.Button != nil {
|
||||||
|
messageType = "button"
|
||||||
|
text = msg.Button.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
case "reaction":
|
||||||
|
if msg.Reaction != nil {
|
||||||
|
messageType = "reaction"
|
||||||
|
text = msg.Reaction.Emoji
|
||||||
|
}
|
||||||
|
|
||||||
|
case "order":
|
||||||
|
if msg.Order != nil {
|
||||||
|
messageType = "order"
|
||||||
|
text = fmt.Sprintf("Order with %d item(s): %s", len(msg.Order.ProductItems), msg.Order.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
if msg.System != nil {
|
||||||
|
messageType = "system"
|
||||||
|
text = msg.System.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
case "unknown":
|
||||||
|
messageType = "unknown"
|
||||||
|
logging.Warn("Received unknown message type", "account_id", c.id, "message_id", msg.ID)
|
||||||
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logging.Warn("Unsupported message type", "account_id", c.id, "type", msg.Type)
|
logging.Warn("Unsupported message type", "account_id", c.id, "type", msg.Type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish message received event
|
// Publish message received event
|
||||||
|
logging.Debug("Publishing message received event",
|
||||||
|
"account_id", c.id,
|
||||||
|
"message_id", msg.ID,
|
||||||
|
"from", msg.From,
|
||||||
|
"type", messageType)
|
||||||
|
|
||||||
c.eventBus.Publish(events.MessageReceivedEvent(
|
c.eventBus.Publish(events.MessageReceivedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
c.id,
|
c.id,
|
||||||
@@ -276,7 +418,10 @@ func getExtensionFromMimeType(mimeType string) string {
|
|||||||
"text/plain": ".txt",
|
"text/plain": ".txt",
|
||||||
"application/json": ".json",
|
"application/json": ".json",
|
||||||
"audio/mpeg": ".mp3",
|
"audio/mpeg": ".mp3",
|
||||||
|
"audio/mp4": ".m4a",
|
||||||
"audio/ogg": ".ogg",
|
"audio/ogg": ".ogg",
|
||||||
|
"audio/amr": ".amr",
|
||||||
|
"audio/opus": ".opus",
|
||||||
}
|
}
|
||||||
|
|
||||||
if ext, ok := extensions[mimeType]; ok {
|
if ext, ok := extensions[mimeType]; ok {
|
||||||
|
|||||||
@@ -116,15 +116,26 @@ type WebhookProfile struct {
|
|||||||
|
|
||||||
// WebhookMessage represents a message in the webhook
|
// WebhookMessage represents a message in the webhook
|
||||||
type WebhookMessage struct {
|
type WebhookMessage struct {
|
||||||
From string `json:"from"` // Sender phone number
|
From string `json:"from"` // Sender phone number
|
||||||
ID string `json:"id"` // Message ID
|
ID string `json:"id"` // Message ID
|
||||||
Timestamp string `json:"timestamp"` // Unix timestamp as string
|
Timestamp string `json:"timestamp"` // Unix timestamp as string
|
||||||
Type string `json:"type"` // "text", "image", "video", "document", etc.
|
Type string `json:"type"` // "text", "image", "video", "document", "audio", "sticker", "location", "contacts", "interactive", "button", "order", "system", "unknown", "reaction"
|
||||||
Text *WebhookText `json:"text,omitempty"`
|
Text *WebhookText `json:"text,omitempty"`
|
||||||
Image *WebhookMediaMessage `json:"image,omitempty"`
|
Image *WebhookMediaMessage `json:"image,omitempty"`
|
||||||
Video *WebhookMediaMessage `json:"video,omitempty"`
|
Video *WebhookMediaMessage `json:"video,omitempty"`
|
||||||
Document *WebhookDocumentMessage `json:"document,omitempty"`
|
Document *WebhookDocumentMessage `json:"document,omitempty"`
|
||||||
Context *WebhookContext `json:"context,omitempty"` // Reply context
|
Audio *WebhookMediaMessage `json:"audio,omitempty"`
|
||||||
|
Sticker *WebhookMediaMessage `json:"sticker,omitempty"`
|
||||||
|
Location *WebhookLocation `json:"location,omitempty"`
|
||||||
|
Contacts []WebhookContactCard `json:"contacts,omitempty"`
|
||||||
|
Interactive *WebhookInteractive `json:"interactive,omitempty"`
|
||||||
|
Button *WebhookButton `json:"button,omitempty"`
|
||||||
|
Reaction *WebhookReaction `json:"reaction,omitempty"`
|
||||||
|
Order *WebhookOrder `json:"order,omitempty"`
|
||||||
|
System *WebhookSystem `json:"system,omitempty"`
|
||||||
|
Context *WebhookContext `json:"context,omitempty"` // Reply context
|
||||||
|
Identity *WebhookIdentity `json:"identity,omitempty"`
|
||||||
|
Referral *WebhookReferral `json:"referral,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebhookText represents a text message
|
// WebhookText represents a text message
|
||||||
@@ -156,6 +167,156 @@ type WebhookContext struct {
|
|||||||
MessageID string `json:"message_id,omitempty"`
|
MessageID string `json:"message_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebhookLocation represents a location message
|
||||||
|
type WebhookLocation struct {
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactCard represents a contact card
|
||||||
|
type WebhookContactCard struct {
|
||||||
|
Addresses []WebhookContactAddress `json:"addresses,omitempty"`
|
||||||
|
Birthday string `json:"birthday,omitempty"`
|
||||||
|
Emails []WebhookContactEmail `json:"emails,omitempty"`
|
||||||
|
Name WebhookContactName `json:"name"`
|
||||||
|
Org WebhookContactOrg `json:"org,omitempty"`
|
||||||
|
Phones []WebhookContactPhone `json:"phones,omitempty"`
|
||||||
|
URLs []WebhookContactURL `json:"urls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactAddress represents a contact address
|
||||||
|
type WebhookContactAddress struct {
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
CountryCode string `json:"country_code,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
Street string `json:"street,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Zip string `json:"zip,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactEmail represents a contact email
|
||||||
|
type WebhookContactEmail struct {
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactName represents a contact name
|
||||||
|
type WebhookContactName struct {
|
||||||
|
FormattedName string `json:"formatted_name"`
|
||||||
|
FirstName string `json:"first_name,omitempty"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
|
MiddleName string `json:"middle_name,omitempty"`
|
||||||
|
Suffix string `json:"suffix,omitempty"`
|
||||||
|
Prefix string `json:"prefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactOrg represents a contact organization
|
||||||
|
type WebhookContactOrg struct {
|
||||||
|
Company string `json:"company,omitempty"`
|
||||||
|
Department string `json:"department,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactPhone represents a contact phone
|
||||||
|
type WebhookContactPhone struct {
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
WaID string `json:"wa_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookContactURL represents a contact URL
|
||||||
|
type WebhookContactURL struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookInteractive represents an interactive message response
|
||||||
|
type WebhookInteractive struct {
|
||||||
|
Type string `json:"type"` // "button_reply", "list_reply"
|
||||||
|
ButtonReply *WebhookButtonReply `json:"button_reply,omitempty"`
|
||||||
|
ListReply *WebhookListReply `json:"list_reply,omitempty"`
|
||||||
|
NfmReply *WebhookNfmReply `json:"nfm_reply,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookButtonReply represents a button reply
|
||||||
|
type WebhookButtonReply struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookListReply represents a list reply
|
||||||
|
type WebhookListReply struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookNfmReply represents a native flow message reply
|
||||||
|
type WebhookNfmReply struct {
|
||||||
|
ResponseJSON string `json:"response_json"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookButton represents a quick reply button
|
||||||
|
type WebhookButton struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookReaction represents a reaction to a message
|
||||||
|
type WebhookReaction struct {
|
||||||
|
MessageID string `json:"message_id"`
|
||||||
|
Emoji string `json:"emoji"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookOrder represents an order
|
||||||
|
type WebhookOrder struct {
|
||||||
|
CatalogID string `json:"catalog_id"`
|
||||||
|
ProductItems []WebhookProductItem `json:"product_items"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookProductItem represents a product in an order
|
||||||
|
type WebhookProductItem struct {
|
||||||
|
ProductRetailerID string `json:"product_retailer_id"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
ItemPrice float64 `json:"item_price"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookSystem represents a system message
|
||||||
|
type WebhookSystem struct {
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"` // "customer_changed_number", "customer_identity_changed", etc.
|
||||||
|
Identity string `json:"identity,omitempty"`
|
||||||
|
NewWaID string `json:"new_wa_id,omitempty"`
|
||||||
|
WaID string `json:"wa_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookIdentity represents identity information
|
||||||
|
type WebhookIdentity struct {
|
||||||
|
Acknowledged bool `json:"acknowledged"`
|
||||||
|
CreatedTimestamp string `json:"created_timestamp"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookReferral represents referral information
|
||||||
|
type WebhookReferral struct {
|
||||||
|
SourceURL string `json:"source_url"`
|
||||||
|
SourceID string `json:"source_id,omitempty"`
|
||||||
|
SourceType string `json:"source_type"`
|
||||||
|
Headline string `json:"headline,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
MediaType string `json:"media_type,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
VideoURL string `json:"video_url,omitempty"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// WebhookStatus represents a message status update
|
// WebhookStatus represents a message status update
|
||||||
type WebhookStatus struct {
|
type WebhookStatus struct {
|
||||||
ID string `json:"id"` // Message ID
|
ID string `json:"id"` // Message ID
|
||||||
@@ -199,26 +360,26 @@ type TokenDebugResponse struct {
|
|||||||
|
|
||||||
// TokenDebugData contains token validation information
|
// TokenDebugData contains token validation information
|
||||||
type TokenDebugData struct {
|
type TokenDebugData struct {
|
||||||
AppID string `json:"app_id"`
|
AppID string `json:"app_id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Application string `json:"application"`
|
Application string `json:"application"`
|
||||||
DataAccessExpiresAt int64 `json:"data_access_expires_at"`
|
DataAccessExpiresAt int64 `json:"data_access_expires_at"`
|
||||||
ExpiresAt int64 `json:"expires_at"`
|
ExpiresAt int64 `json:"expires_at"`
|
||||||
IsValid bool `json:"is_valid"`
|
IsValid bool `json:"is_valid"`
|
||||||
IssuedAt int64 `json:"issued_at,omitempty"`
|
IssuedAt int64 `json:"issued_at,omitempty"`
|
||||||
Scopes []string `json:"scopes"`
|
Scopes []string `json:"scopes"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PhoneNumberDetails represents phone number information from the API
|
// PhoneNumberDetails represents phone number information from the API
|
||||||
type PhoneNumberDetails struct {
|
type PhoneNumberDetails struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
VerifiedName string `json:"verified_name"`
|
VerifiedName string `json:"verified_name"`
|
||||||
CodeVerificationStatus string `json:"code_verification_status"`
|
CodeVerificationStatus string `json:"code_verification_status"`
|
||||||
DisplayPhoneNumber string `json:"display_phone_number"`
|
DisplayPhoneNumber string `json:"display_phone_number"`
|
||||||
QualityRating string `json:"quality_rating"`
|
QualityRating string `json:"quality_rating"`
|
||||||
PlatformType string `json:"platform_type"`
|
PlatformType string `json:"platform_type"`
|
||||||
Throughput ThroughputInfo `json:"throughput"`
|
Throughput ThroughputInfo `json:"throughput"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThroughputInfo contains throughput information
|
// ThroughputInfo contains throughput information
|
||||||
@@ -228,8 +389,8 @@ type ThroughputInfo struct {
|
|||||||
|
|
||||||
// BusinessAccountDetails represents business account information
|
// BusinessAccountDetails represents business account information
|
||||||
type BusinessAccountDetails struct {
|
type BusinessAccountDetails struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TimezoneID string `json:"timezone_id"`
|
TimezoneID string `json:"timezone_id"`
|
||||||
MessageTemplateNamespace string `json:"message_template_namespace,omitempty"`
|
MessageTemplateNamespace string `json:"message_template_namespace,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user