# go-wxpush sends WeChat template messages from a single Go binary

> go-wxpush is a small Go service that turns a WeChat public platform sandbox test account into an HTTP endpoint that scripts can call to deliver template messages. It deploys in one command and is easy to misconfigure, and it is built for personal alerting rather than production messaging.

**hezhizheng/go-wxpush** — 极简且免费的微信消息推送服务 (基于golang)

- Repository: https://github.com/hezhizheng/go-wxpush
- Website: https://push.hzzio.top
- Stars: 1,662 · Forks: 191
- Language: Go
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/hezhizheng-go-wxpush

## go-wxpush turns a WeChat test account into an HTTP push endpoint

The problem go-wxpush addresses is narrow and concrete: getting a native WeChat notification, with sound, onto a phone without assembling a full WeChat bot stack. The project is a single Go binary that holds the credentials of a WeChat public platform sandbox test account and exposes one HTTP endpoint, /wxsend, that other programs can call to deliver a template message. A test account is the pivot. The WeChat sandbox hands you an appid, an appsecret, a user OpenID for the account you follow, and a template ID, and go-wxpush wires those values into an outbound call so that a plain GET or POST produces the template message. The audience is self-hosters who want a scriptable alert path into a phone: a deploy script reporting a failed migration, a cron job reporting a finished backup, a probe reporting that a service came back. It is not a chatbot framework and not a message queue. There is one endpoint, one message shape, a title and a body per push, and a page that shows the message detail when the recipient taps the notification. The push itself is a request against a single path.

```
http://127.0.0.1:5566/wxsend
```

## Startup flags set the defaults, request parameters override them

The parameter table is the entire configuration surface, and it splits cleanly in two. The same names work as startup flags and as query parameters on /wxsend, where they are described as temporary overrides of the default AppID, AppSecret, recipient OpenID, template ID and redirect URL. That means one running instance can hold a default recipient and a default template while a caller redirects a single message to a different OpenID without any restart. Title and content are also accepted per request, which is what lets a script assemble its own text instead of relying on the startup values. Two details are easy to miss. The tz parameter defaults to the China Standard Time offset and appears in the container command line, so the timestamp carried by a message is not necessarily the caller's local time. And port is marked as command-line only, so the listen port is fixed when the process starts and cannot be changed per request. The container default line lists every flag the binary understands, with empty placeholders for the values you are expected to supply. Beyond GET, the service accepts POST on the same path, which is the shape most webhook integrations expect.

```
POST
```

```
{
  "Content-Type": "application/json"
}
```

The body is a JSON object carrying the same parameter names as the query string, so a caller can send title and content and let the stored defaults cover the credentials.

## Building or pulling the image is the whole installation

There is no package manager step and no dependency install for the end user. The release page ships prebuilt executables, and the deployment guide also describes compiling cross-platform binaries with gox, stripping the symbol table and trimming paths.

```
gox -osarch="windows/amd64" -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}"
gox -osarch="darwin/amd64" -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}"
gox -osarch="linux/amd64" -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}"
gox -osarch="linux/arm64" -ldflags "-s -w" -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}"
```

The generated file is directly executable. The container route is shorter. The Dockerfile copies a compiled linux_amd64 binary into a scratch image with no base filesystem, which is why the image is described as a couple of megabytes, and it sets the port, the credential placeholders and the base URL as the default command.

```
CMD ["-port", "5566", "-appid", "","-secret","","-userid","","-template_id","","-base_url","https://push.hzz.cool/detail","-title","","-content","","-tz",""]
```

If you build locally you place the compiled binary next to the Dockerfile and tag the result.

```
docker build -t go-wxpush:v2 .
```

If you want the published image, pull the v4 tag first, because the guide notes that a redeploy should start from the newest image.

```
docker pull hezhizheng/go-wxpush:v4
```

## The detail page and the base_url flag point in opposite directions

go-wxpush serves its own message detail page, the page a template message jumps to when the recipient taps it. The documentation gives the local address as http://127.0.0.1:5566/detail and explains that a deployment with a public address should set the base_url parameter to the matching host without the /detail suffix, since the service appends that part itself. Two different defaults ship in the repository, and they are not the same host. The container command line carries https://push.hzz.cool/detail, while the command line example in the documentation uses https://push.hzzio.top. A self-hosted instance that never sets the flag therefore points recipients at a page that is not its own. The detail template lives in msg_detail.html at the top level of the repository, next to main.go and go.mod, and the Go module is named go-wxpush.

## A template that loses its prefix silently loses the body

The most likely first failure is not in the Go code, it is in the WeChat template. The deployment notes warn that the template content field must be written as a short label followed by a colon, a space and the {{content.DATA}} placeholder, and that the message body will not appear if the field contains only {{content.DATA}} with nothing in front of it. This is the only template level constraint the project documents, and it is easy to miss because the service still reports success. The documented error paths are the credential and call failures: the service returns a status code and error text when the token is wrong, when a required parameter is missing, or when the WeChat interface call itself fails. A successful push returns an errcode of 0 once the message reaches at least one configured user, so a green result confirms dispatch and nothing about the template formatting.

## The endpoint carries the appsecret in the query string

Everything needed to send a message travels either in the process arguments or in the request, and the request form puts appid and secret in the query string. A reachable /wxsend is therefore a credential exposure risk, because a URL with an appsecret in it tends to be copied into access logs, proxy logs, shell history and chat windows. The documentation describes no token, no shared secret and no rate limiting on the endpoint, so an internet facing deployment has to add its own access control in front of it. The project states a daily allowance of 100,000 pushes, framed as more than a personal user needs, and it offers a shared hosted instance at https://push.hzzio.top with a note not to abuse it. The tighter ceiling sits on the WeChat side: this is built on a sandbox test account, so the traffic is bounded by what a test account tolerates rather than by any paid subscription.

## Self-hosted binary against the shared instance at push.hzzio.top

Two routes are offered and they differ in where the credentials live. Self-hosting keeps the appid, secret, userid and template_id on a machine you control, gives you your own detail page and your own base_url, and lets you pick the port. The hosted option moves the WeChat credentials to the maintainer and gives you an endpoint with no local process, which is the fastest way to try the idea but leaves the message path and the detail page on infrastructure you do not control. The project documentation also links related work it credits, including frankiejun/wxpush, wxpushSkin and a Gotify WeChat plugin. It does not describe how their internals differ, so treat that list as pointers rather than as a comparison. The licensing side is simple: LICENSE.txt carries the MIT license, and nothing in the documentation attaches extra conditions to running your own instance.

## Conclusion

go-wxpush is worth adopting if you already run a Go service and want a phone notification from a script without building a WeChat bot. It is the wrong tool if you need delivery guarantees, multi-tenant routing, or an authenticated endpoint. Before deploying, check that your sandbox template carries leading wording in front of {{content.DATA}}, and put a reverse proxy with access control in front of /wxsend, since the documented query parameters carry the appsecret.

## FAQ

### How do I install and run go-wxpush?

The project ships prebuilt executables on its releases page, and the container route is a single published image. The deployment guide also shows how to compile cross-platform binaries with gox, and notes that a redeploy should pull the newest image tag first.

### Where do appid, secret, userid and template_id come from?

All four come from the WeChat public platform sandbox test account. You apply for a test account, read the appid and appsecret from it, follow the test public account to obtain the userid, and add a test template to get the template ID.

### Can go-wxpush send messages with POST instead of GET?

Yes. Besides GET, the service accepts POST on the same /wxsend address, which suits webhook style automation. The request needs a Content-Type of application/json, and the body is a JSON object with the same parameter names as the query string.

### Why does my pushed message arrive with no body text?

The template content field usually has the wrong shape. The deployment notes require a short label in front of the {{content.DATA}} placeholder, and warn that a field containing only the placeholder produces a message with no visible content even though the push itself succeeds.

## Sources

- [hezhizheng/go-wxpush on GitHub](https://github.com/hezhizheng/go-wxpush)
- [License: MIT](https://github.com/hezhizheng/go-wxpush/blob/master/LICENSE)
- [Project website](https://push.hzzio.top)
- [README](https://github.com/hezhizheng/go-wxpush/blob/master/README.md)
- [Releases](https://github.com/hezhizheng/go-wxpush/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hezhizheng-go-wxpush
