Summary
Two whatsmeow capabilities are effectively present in the codebase but not surfaced to API consumers, so CRMs built on top of Evolution Go can't reach feature parity with the Baileys-based Evolution API for them:
- Contact presence ("online" / last-seen) — there is no way to
SubscribePresence, so events.Presence never fires.
- Edited messages arrive encrypted and undecrypted — the new text is delivered as a raw
secretEncryptedMessage envelope, so consumers can't read what the message was edited to.
Both are small, additive changes. Happy to open a PR for either/both if the approach looks right to you.
1. Expose SubscribePresence → enables contact "online"/last-seen
whatsmeow only emits events.Presence for JIDs you've explicitly subscribed to via Client.SubscribePresence(ctx, jid). A grep of the repo shows SubscribePresence is never called, and there's no route to trigger it. The rest of the plumbing already exists:
- the
Presence event dispatcher → webhook ({event:"Presence", state, data:{From, Unavailable, LastSeen}}) is already implemented in pkg/whatsmeow/service/whatsmeow.go;
- the
PRESENCE subscription filter already exists.
Request: a new instance-scoped route, e.g. POST /user/presence/subscribe with body { "number": "5521..." } (nice-to-have: also accept { "numbers": [...] } for batching the top N conversations of an inbox):
- normalize to a canonical JID (same helper used by
/message/presence);
- send our own presence first —
client.SendPresence(types.PresenceAvailable) — since whatsmeow only receives presence updates after you've broadcast your own (same rationale already noted in pkg/message/service/message_service.go around the ChatPresence handler);
- call
client.SubscribePresence(ctx, jid) and return {data:{subscribed:true}}.
With the subscribe in place, the existing Presence → webhook path just starts flowing. No other change needed.
2. Decrypt edited messages (currently the new text is unreadable)
When a contact edits a text message, the event arrives with Info.Edit == "1" and the new text encrypted inside Message.secretEncryptedMessage (secretEncType: 2), with the original message key under secretEncryptedMessage.targetMessageKey. Because this is never decrypted, downstream consumers receive an opaque envelope instead of the edited text.
Real captured payload (redacted):
{
"event": "Message",
"data": {
"Info": { "Edit": "1", "ID": "3A3EFD...", "IsFromMe": false, "Type": "text", "Timestamp": "..." },
"IsEdit": false,
"Message": {
"secretEncryptedMessage": {
"encIV": "…", "encPayload": "…", "secretEncType": 2,
"targetMessageKey": { "ID": "3A35AE...", "fromMe": true, "remoteJID": "…" }
}
}
}
}
Two observations:
data.IsEdit is false even for an edit — the only reliable signal is Info.Edit == "1". If that's intended, it would help to document it; if not, it may be a small bug worth fixing.
- The message handler already decrypts poll votes with
DecryptPollVote (in pkg/whatsmeow/service/whatsmeow.go, in the same event-processing block that handles PollUpdateMessage). The edit path could mirror that: when evt.Info.Edit indicates an edit (or secretEncryptedMessage is present), decrypt the secret message and emit the resulting editedMessage / plaintext in the webhook (e.g. as protocolMessage.editedMessage or a decoded Message), so consumers can update the original bubble.
Request: decrypt the secretEncryptedMessage for edits (as already done for poll votes) and surface the plaintext new text in the webhook payload.
Environment
- Evolution Go build "Evolution GO 1.0" (self-hosted), consumed by an external CRM via webhooks + REST.
- Baileys-based Evolution API already surfaces both of these (contact online + decoded edits), so this is purely about reaching parity on the Go engine.
Thanks for the project! Glad to send PRs for these if you'd point me at the preferred approach.
Summary
Two
whatsmeowcapabilities are effectively present in the codebase but not surfaced to API consumers, so CRMs built on top of Evolution Go can't reach feature parity with the Baileys-based Evolution API for them:SubscribePresence, soevents.Presencenever fires.secretEncryptedMessageenvelope, so consumers can't read what the message was edited to.Both are small, additive changes. Happy to open a PR for either/both if the approach looks right to you.
1. Expose
SubscribePresence→ enables contact "online"/last-seenwhatsmeow only emits
events.Presencefor JIDs you've explicitly subscribed to viaClient.SubscribePresence(ctx, jid). A grep of the repo showsSubscribePresenceis never called, and there's no route to trigger it. The rest of the plumbing already exists:Presenceevent dispatcher → webhook ({event:"Presence", state, data:{From, Unavailable, LastSeen}}) is already implemented inpkg/whatsmeow/service/whatsmeow.go;PRESENCEsubscription filter already exists.Request: a new instance-scoped route, e.g.
POST /user/presence/subscribewith body{ "number": "5521..." }(nice-to-have: also accept{ "numbers": [...] }for batching the top N conversations of an inbox):/message/presence);client.SendPresence(types.PresenceAvailable)— since whatsmeow only receives presence updates after you've broadcast your own (same rationale already noted inpkg/message/service/message_service.goaround theChatPresencehandler);client.SubscribePresence(ctx, jid)and return{data:{subscribed:true}}.With the subscribe in place, the existing
Presence→ webhook path just starts flowing. No other change needed.2. Decrypt edited messages (currently the new text is unreadable)
When a contact edits a text message, the event arrives with
Info.Edit == "1"and the new text encrypted insideMessage.secretEncryptedMessage(secretEncType: 2), with the original message key undersecretEncryptedMessage.targetMessageKey. Because this is never decrypted, downstream consumers receive an opaque envelope instead of the edited text.Real captured payload (redacted):
{ "event": "Message", "data": { "Info": { "Edit": "1", "ID": "3A3EFD...", "IsFromMe": false, "Type": "text", "Timestamp": "..." }, "IsEdit": false, "Message": { "secretEncryptedMessage": { "encIV": "…", "encPayload": "…", "secretEncType": 2, "targetMessageKey": { "ID": "3A35AE...", "fromMe": true, "remoteJID": "…" } } } } }Two observations:
data.IsEditisfalseeven for an edit — the only reliable signal isInfo.Edit == "1". If that's intended, it would help to document it; if not, it may be a small bug worth fixing.DecryptPollVote(inpkg/whatsmeow/service/whatsmeow.go, in the same event-processing block that handlesPollUpdateMessage). The edit path could mirror that: whenevt.Info.Editindicates an edit (orsecretEncryptedMessageis present), decrypt the secret message and emit the resultingeditedMessage/ plaintext in the webhook (e.g. asprotocolMessage.editedMessageor a decodedMessage), so consumers can update the original bubble.Request: decrypt the
secretEncryptedMessagefor edits (as already done for poll votes) and surface the plaintext new text in the webhook payload.Environment
Thanks for the project! Glad to send PRs for these if you'd point me at the preferred approach.