DeBox Bot Go SDK Webhook Guide
Source repository:
This document covers DeBox bot Webhook integration for Go services. It is intended for backend developers who need a production-grade callback receiver, payload parser, and reply flow.
1. Webhook vs Long Polling (strictly mutually exclusive)
This guide implements Webhook mode. While a Webhook URL is configured in BotMother, Webhook is the only effective receiving path and Long Polling cannot receive messages. To return to Long Polling, clear the Webhook settings first.
For the complete mode-selection flow, see DeBox Bot Development Overview. For Long Polling implementation, see DeBox Bot Go SDK.
2. When to use Webhook
Use Webhook when:
- Your service is publicly reachable over HTTP/HTTPS.
- You need lower latency than polling.
- You want a push-based event flow.
- You need server-side handling for media messages or group join events.
3. BotMother Webhook configuration
In the BotMother Webhook settings, configure Webhook URL and Webhook Key.
Example Webhook URL:
https://your-domain.com/bot/webhook
Also ensure:
Webhook URLis stable, publicly reachable, and uses HTTPS.- Your backend validates that
X-API-KEYon each callback matches the configuredWebhook Key. - If you change
Webhook Key, update the value used by your backend validator at the same time.
4. Delivery contract
4.1 HTTP request
DeBox sends:
- Method:
POST - Content-Type:
application/json - Header:
X-API-KEY: <webhook-key>
Recommendations:
- Reject requests with missing or invalid
X-API-KEY. - Return
200 OKonly after your application has accepted the event. - Implement idempotency using your own event deduplication strategy if needed.
4.2 Top-level payload fields
Ordinary messages and group join events use the flattened payload below. Button clicks use a separate nested structure; see button events.
| Field | Type | Required | Description |
|---|---|---|---|
from_user_id | string | Yes | The event sender's DeBox user_id value. It is the same as the sender's uid and invite_code. In private chat, this is the other user. In group chat, this is the speaking user. For current join-group events, this is the joined member. |
to_user_id | string | Yes | The target bot account's DeBox user_id value; the same value as that account's uid and invite_code. |
name | string | No | Display name of from_user_id. |
pic | string | No | Avatar URL of from_user_id. |
address | string | No | Wallet address of from_user_id. |
language | string | No | User language, if available. |
group_id | string | No | DeBox group ID or chatroom ID. Empty for private chat. |
parse_mode | string | Yes | Type of the current payload, such as text, image, video, file, link, event:joinGroup. |
message | string | Yes | Normalized message body. Meaning depends on parse_mode. |
message_raw | string | Yes | Original content after DeBox parsing. Meaning depends on parse_mode. |
mention_users | array<object> | No | Mentioned users in group text messages. Present when the source message contains mentions. |
mention_users item structure:
| Field | Type | Description |
|---|---|---|
user_id | string | The mentioned user's DeBox user_id value; the same value as that user's uid and invite_code |
name | string | Display name |
pic | string | Avatar URL |
address | string | Wallet address |
4.3 parse_mode values
parse_mode | Meaning | message / message_raw |
|---|---|---|
text | Plain text or command-like text | Message text. Mentions are removed from message and preserved in message_raw. |
image | Image message | Image URL |
video | Video message | Video URL |
file | File message | File URL |
link | Shared link / dapp share | Shared link URL |
event:joinGroup | Group join event | Current joined member's user_id value |
Important behavior notes:
- For
image,video, andfile, the bot receives a URL string, not the binary file body. - For
text,messageis normalized text, whilemessage_rawpreserves the original text. - For
event:joinGroup, current payload semantics are event-oriented rather than conversational:group_idis the target group.from_user_idis the joined member exposed by the current callback implementation.messagecontains the same identifier value as the joined member'suser_id.
4.4 Button click events
Button events use a top-level callback_query, unlike ordinary flattened messages. Non-empty response_data takes precedence over legacy callback_data and is exposed as callback_query.data. If both are empty, the payload contains a top-level message object instead of callback_query.
- Both
callback_query.fromandcallback_query.message.fromidentify the clicking user. callback_query.messagepreserves the original message ID, text, andparse_mode.- Group chats use
{"id":"group gid","type":"group"}; private chats use{"id":"clicking user code","type":"private"}. - Webhook sends the object below directly, without a polling record
id,ok, orresultenvelope. Polling exposes the same event contents. - Delivery has a 5-second timeout, does not follow redirects, and only accepts 2xx responses. Timeouts, connection failures, and non-2xx responses discard the event with a log entry: there is no retry or polling fallback. The App still receives success, which does not confirm that the bot received the event.
{
"callback_query": {
"from": {
"user_id": "uvg2p6ho",
"name": "Alice",
"pic": "",
"address": "",
"signature": ""
},
"message": {
"message_id": "01HXYZABCDEF1234567890",
"from": {
"user_id": "uvg2p6ho",
"name": "Alice",
"pic": "",
"address": "",
"signature": ""
},
"chat": {
"id": "cc0onr82",
"type": "group"
},
"text": "Choose an action",
"parse_mode": "HTML"
},
"data": "detail"
}
}
Receivers must check top-level callback_query or an object-valued message before routing ordinary messages by parse_mode. The flattened structs and example in section 6 only handle ordinary messages and join events; add this branch when receiving button events.
5. Payload examples by message type
5.1 Text message
{
"from_user_id": "u_alice",
"to_user_id": "u_bot",
"name": "Alice",
"pic": "https://cdn.example.com/alice.png",
"address": "0x1234",
"language": "en",
"group_id": "cc0onr82",
"parse_mode": "text",
"message": "hello bot",
"message_raw": "@MyBot hello bot",
"mention_users": [
{
"user_id": "u_bot",
"name": "MyBot",
"pic": "https://cdn.example.com/bot.png",
"address": ""
}
]
}
5.2 Image message
{
"from_user_id": "u_alice",
"to_user_id": "u_bot",
"name": "Alice",
"language": "en",
"group_id": "cc0onr82",
"parse_mode": "image",
"message": "https://cdn.example.com/image.png",
"message_raw": "https://cdn.example.com/image.png"
}
5.3 Video message
{
"from_user_id": "u_alice",
"to_user_id": "u_bot",
"name": "Alice",
"language": "en",
"group_id": "cc0onr82",
"parse_mode": "video",
"message": "https://cdn.example.com/video.mp4",
"message_raw": "https://cdn.example.com/video.mp4"
}
5.4 File message
{
"from_user_id": "u_alice",
"to_user_id": "u_bot",
"name": "Alice",
"language": "en",
"group_id": "cc0onr82",
"parse_mode": "file",
"message": "https://cdn.example.com/report.pdf",
"message_raw": "https://cdn.example.com/report.pdf"
}
5.5 Group join event
{
"from_user_id": "u_new_member",
"to_user_id": "u_bot",
"name": "New Member",
"pic": "https://cdn.example.com/new-member.png",
"address": "0xabcd",
"language": "en",
"group_id": "cc0onr82",
"parse_mode": "event:joinGroup",
"message": "u_new_member",
"message_raw": "u_new_member"
}
Use this event when your bot needs to:
- Send onboarding messages to new members.
- Trigger group welcome workflows.
- Record membership events in your own backend.
6. Go receiver design
Recommended server flow:
- Verify
X-API-KEY. - Parse JSON into a typed struct.
- Route logic by
parse_mode. - Infer chat context from
group_id. - Use Go SDK to send reply messages.
6.1 Suggested payload structs
type WebhookUser struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Pic string `json:"pic"`
Address string `json:"address"`
}
type WebhookPayload struct {
FromUserID string `json:"from_user_id"`
ToUserID string `json:"to_user_id"`
Name string `json:"name"`
Pic string `json:"pic"`
Address string `json:"address"`
Language string `json:"language"`
GroupID string `json:"group_id"`
ParseMode string `json:"parse_mode"`
Message string `json:"message"`
MessageRaw string `json:"message_raw"`
MentionUsers []WebhookUser `json:"mention_users"`
}
6.2 Full sample (Gin + Go SDK)
package main
import (
"fmt"
"net/http"
"os"
"github.com/gin-gonic/gin"
boxbotapi "github.com/debox-pro/debox-chat-go-sdk/boxbotapi"
)
type WebhookUser struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Pic string `json:"pic"`
Address string `json:"address"`
}
type WebhookPayload struct {
FromUserID string `json:"from_user_id"`
ToUserID string `json:"to_user_id"`
Name string `json:"name"`
Pic string `json:"pic"`
Address string `json:"address"`
Language string `json:"language"`
GroupID string `json:"group_id"`
ParseMode string `json:"parse_mode"`
Message string `json:"message"`
MessageRaw string `json:"message_raw"`
MentionUsers []WebhookUser `json:"mention_users"`
}
func main() {
webhookKey := os.Getenv("DEBOX_WEBHOOK_KEY")
apiKey := os.Getenv("DEBOX_BOT_API_KEY")
apiSecret := os.Getenv("DEBOX_BOT_API_SECRET")
if webhookKey == "" || apiKey == "" || apiSecret == "" {
panic("missing required env: DEBOX_WEBHOOK_KEY / DEBOX_BOT_API_KEY / DEBOX_BOT_API_SECRET")
}
bot, err := boxbotapi.NewBotAPI(apiKey, apiSecret)
if err != nil {
panic(err)
}
r := gin.Default()
r.POST("/bot/webhook", func(c *gin.Context) {
if c.GetHeader("X-API-KEY") != webhookKey {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid webhook key"})
return
}
var payload WebhookPayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
chatType := "private"
chatID := payload.FromUserID
if payload.GroupID != "" {
chatType = "group"
chatID = payload.GroupID
}
switch payload.ParseMode {
case "text":
reply := boxbotapi.NewMessage(chatID, chatType, "Received: "+payload.Message)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
case "image":
reply := boxbotapi.NewMessage(chatID, chatType, "Image received: "+payload.Message)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
case "video":
reply := boxbotapi.NewMessage(chatID, chatType, "Video received: "+payload.Message)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
case "file":
reply := boxbotapi.NewMessage(chatID, chatType, "File received: "+payload.Message)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
case "event:joinGroup":
welcome := fmt.Sprintf("Welcome %s to the group.", payload.FromUserID)
reply := boxbotapi.NewMessage(chatID, chatType, welcome)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
default:
reply := boxbotapi.NewMessage(chatID, chatType, "Unsupported payload type: "+payload.ParseMode)
reply.ParseMode = boxbotapi.ModeText
_, err = bot.Send(reply)
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
})
_ = r.Run(":8080")
}
7. Sending media replies with Go SDK
The complete outbound message contract is in the OpenAPI message fields. The following example only shows how to send media with the Go SDK.
image := boxbotapi.NewMessage("cc0onr82", "group", "https://cdn.example.com/welcome.png")
image.ParseMode = boxbotapi.ModeImage
_, _ = bot.Send(image)
video := boxbotapi.NewMessage("cc0onr82", "group", "https://cdn.example.com/intro.mp4")
video.ParseMode = boxbotapi.ModeVideo
_, _ = bot.Send(video)
file := boxbotapi.NewMessage("cc0onr82", "group", "https://cdn.example.com/guide.pdf")
file.ParseMode = boxbotapi.ModeFile
_, _ = bot.Send(file)
8. Production recommendations
- Put webhook authentication and JSON parsing in middleware or a shared adapter.
- Log
parse_mode,from_user_id,to_user_id, andgroup_idfor traceability. - Do not trust media URLs indefinitely; download or process them according to your retention policy.
- For group bots, branch business logic by
group_idinstead of assuming one bot serves one group only. - Keep reply generation asynchronous if your downstream processing is slow.
9. Common pitfalls
- Webhook configured but no updates received:
- Check endpoint reachability, TLS, server logs, and
X-API-KEYverification.
- Check endpoint reachability, TLS, server logs, and
- Webhook and Long Polling both enabled:
- Only one receive mode is valid. With Webhook enabled, polling is not a valid receive path.
- Treating media payloads as binary uploads:
- Webhook delivers media URLs, not file streams.
- Ignoring
parse_mode:- Always branch on
parse_mode, not only on the existence ofmessage.
- Always branch on
- Local development without a public domain:
- Use a tunnel to expose your local endpoint temporarily.
10. Related docs
- Long Polling guide: DeBox Bot Go SDK