package androidsmsgateway import ( "encoding/json" "errors" "time" ) // WebhookEvent is an interface for all webhook event types. type WebhookEvent interface { GetEvent() string } // Webhook represents a webhook notification for an SMS sent event. type Webhook struct { DeviceID string `json:"deviceId"` Event string `json:"event"` ID string `json:"id"` Payload json.RawMessage `json:"payload"` WebhookID string `json:"webhookId"` XSignature string `json:"x-signature"` XTimestamp int64 `json:"x-timestamp"` } // WebhookSent represents a webhook notification for an SMS sent event. type WebhookSent struct { DeviceID string `json:"deviceId" csv:"deviceId"` Event string `json:"event" csv:"event"` ID string `json:"id" csv:"id"` Payload WebhookSentPayload `json:"payload" csv:",inline"` WebhookID string `json:"webhookId" csv:"webhookId"` XSignature string `json:"x-signature" csv:"-"` XTimestamp int64 `json:"x-timestamp" csv:"-"` } // GetEvent marks WebhookSent as part of the WebhookEvent interface. func (w *WebhookSent) GetEvent() string { return w.Event } // WebhookSentPayload contains details about the sent SMS. type WebhookSentPayload struct { MessageID string `json:"messageId" csv:"messageId"` PartsCount int `json:"partsCount" csv:"partsCount"` PhoneNumber string `json:"phoneNumber" csv:"phoneNumber"` SentAt time.Time `json:"sentAt" csv:"sentAt"` } // WebhookDelivered represents a webhook notification for an SMS delivered event. type WebhookDelivered struct { DeviceID string `json:"deviceId"` Event string `json:"event"` ID string `json:"id"` Payload WebhookDeliveredPayload `json:"payload"` WebhookID string `json:"webhookId"` XSignature string `json:"x-signature"` XTimestamp int64 `json:"x-timestamp"` } // GetEvent marks WebhookDelivered as part of the WebhookEvent interface. func (w *WebhookDelivered) GetEvent() string { return w.Event } // WebhookDeliveredPayload contains details about the delivered SMS. type WebhookDeliveredPayload struct { DeliveredAt time.Time `json:"deliveredAt"` MessageID string `json:"messageId"` PhoneNumber string `json:"phoneNumber"` } // WebhookReceived represents a webhook notification for an SMS received event. type WebhookReceived struct { DeviceID string `json:"deviceId" csv:"deviceId"` Event string `json:"event" csv:"event"` ID string `json:"id" csv:"id"` Payload WebhookReceivedPayload `json:"payload" csv:",inline"` WebhookID string `json:"webhookId" csv:"webhookId"` XSignature string `json:"x-signature" csv:"-"` XTimestamp int64 `json:"x-timestamp" csv:"-"` } // GetEvent marks WebhookDelivered as part of the WebhookEvent interface. func (w *WebhookReceived) GetEvent() string { return w.Event } // WebhookReceivedPayload contains details about the received SMS. type WebhookReceivedPayload struct { Message string `json:"message" csv:"message"` MessageID string `json:"messageId" csv:"messageId"` PhoneNumber string `json:"phoneNumber" csv:"phoneNumber"` ReceivedAt time.Time `json:"receivedAt" csv:"receivedAt"` SimNumber int `json:"simNumber" csv:"simNumber"` } // WebhookPing represents a system:ping webhook event. type WebhookPing struct { DeviceID string `json:"deviceId" csv:"deviceId"` Event string `json:"event" csv:"event"` ID string `json:"id" csv:"id"` Payload WebhookPingPayload `json:"payload" csv:"payload"` WebhookID string `json:"webhookId" csv:"webhookId"` PingedAt time.Time `json:"pingedAt,omitempty" csv:"pingedAt"` XSignature string `json:"x-signature" csv:"-"` XTimestamp int64 `json:"x-timestamp" csv:"-"` } // GetEvent marks WebhookPing as part of the WebhookEvent interface. func (w *WebhookPing) GetEvent() string { return w.Event } // WebhookPingPayload contains the health data reported by a system:ping event. // MarshalText serialises the payload as a compact JSON string so that csvutil // stores it as a single "payload" column rather than expanding every health check. type WebhookPingPayload struct { Health DeviceHealth `json:"health"` } // MarshalText implements encoding.TextMarshaler for CSV serialisation. func (p WebhookPingPayload) MarshalText() ([]byte, error) { return json.Marshal(p) } // MarshalJSON prevents MarshalText from being used during JSON serialisation, // ensuring WebhookPingPayload is always encoded as a full JSON object. func (p WebhookPingPayload) MarshalJSON() ([]byte, error) { type alias WebhookPingPayload return json.Marshal(alias(p)) } // DeviceHealth is the top-level health object inside a system:ping payload. // Named fields use colon json tags matching the API key names inside "checks". type DeviceHealth struct { Checks DeviceChecks `json:"checks"` ReleaseID int `json:"releaseId"` Status string `json:"status"` Version string `json:"version"` } // DeviceChecks holds the individual health checks reported by the device. // Go field names are camelCase; json tags carry the colon-delimited API key names. type DeviceChecks struct { BatteryCharging HealthCheck `json:"battery:charging"` BatteryLevel HealthCheck `json:"battery:level"` ConnectionCellular HealthCheck `json:"connection:cellular"` ConnectionStatus HealthCheck `json:"connection:status"` ConnectionTransport HealthCheck `json:"connection:transport"` MessagesFailed HealthCheck `json:"messages:failed"` } // HealthCheck represents a single named health check result. type HealthCheck struct { Description string `json:"description"` ObservedUnit string `json:"observedUnit"` ObservedValue float64 `json:"observedValue"` Status string `json:"status"` } // Decode decodes the raw Payload based on the Event field and returns the appropriate WebhookEvent. func Decode(webhook *Webhook) (WebhookEvent, error) { switch webhook.Event { case "system:ping": var ping WebhookPing ping.DeviceID = webhook.DeviceID ping.Event = webhook.Event ping.ID = webhook.ID ping.WebhookID = webhook.WebhookID ping.XSignature = webhook.XSignature ping.XTimestamp = webhook.XTimestamp if err := json.Unmarshal(webhook.Payload, &ping.Payload); err != nil { return nil, errors.New("failed to decode system:ping payload: " + err.Error()) } return &ping, nil case "sms:sent": var sent WebhookSent sent.DeviceID = webhook.DeviceID sent.Event = webhook.Event sent.ID = webhook.ID sent.WebhookID = webhook.WebhookID sent.XSignature = webhook.XSignature sent.XTimestamp = webhook.XTimestamp if err := json.Unmarshal(webhook.Payload, &sent.Payload); err != nil { return nil, errors.New("failed to decode sms:sent payload: " + err.Error()) } return &sent, nil case "sms:delivered": var delivered WebhookDelivered delivered.DeviceID = webhook.DeviceID delivered.Event = webhook.Event delivered.ID = webhook.ID delivered.WebhookID = webhook.WebhookID delivered.XSignature = webhook.XSignature delivered.XTimestamp = webhook.XTimestamp if err := json.Unmarshal(webhook.Payload, &delivered.Payload); err != nil { return nil, errors.New("failed to decode sms:delivered payload: " + err.Error()) } return &delivered, nil case "sms:received": var received WebhookReceived received.DeviceID = webhook.DeviceID received.Event = webhook.Event received.ID = webhook.ID received.WebhookID = webhook.WebhookID received.XSignature = webhook.XSignature received.XTimestamp = webhook.XTimestamp if err := json.Unmarshal(webhook.Payload, &received.Payload); err != nil { return nil, errors.New("failed to decode sms:received payload: " + err.Error()) } return &received, nil default: return nil, errors.New("unknown event type: " + webhook.Event) } }