English
What is a Webhook? What specifications are there? How do you implement it in code? (including Golang examples)

Preface
You may have already heard the term Webhook, or seen it in the configuration interfaces of GitHub, Slack, DingTalk, or Feishu. It sounds like an “advanced” technical concept, but if you have never actually used it, it can indeed be confusing when you first encounter it—what exactly is it? How is it different from ordinary API calls? Why do so many platforms support it?
This article will start from the most fundamental concepts, using easy-to-understand analogies to help you understand the essence of Webhooks, then take an in-depth look at what “specifications” (or industry consensus) currently exist for them, and finally provide complete code examples in Go so that you can start writing code right after reading.
1. What is a Webhook?
1.1 A relatable analogy
Imagine going to a very busy restaurant for a meal. After you order, there are two ways to know your food is ready:
Method 1 (Polling): You go to the counter every 2 minutes and ask: "Is my meal ready?" — This is Polling (Polling). You keep proactively asking, wasting your own time and disturbing the waiter.
Method 2 (Webhook): You leave your phone number with the server and tell them, "Call me when the food is ready." Then you can sit back and scroll on your phone with peace of mind. When the phone rings, you know you can go pick up your food. — This is Webhook.
A webhook is essentially just An event-driven HTTP callback mechanism. In more technical terms: when a certain event occurs, Application A automatically sends an HTTP request (usually POST) to a URL pre-specified by Application B, carrying the relevant data in the request body.
In one simple sentence:Webhook is a "reverse API"—it's not that you actively request data, but that the data source actively pushes data to you..
1.2 Webhook vs. Traditional API
This is the most critical step in understanding Webhooks.
| Comparison dimensions | Webhook (event-driven) | Traditional API (request-driven) |
|---|---|---|
| Communication direction | Push (Push): The data source actively sends the data to you | Pull (Pull): You proactively request data from the data source |
| Trigger method | by Event Trigger (code commit, payment completed, new user registration, etc.) | by Your request Triggered, requiring scheduled or on-demand invocation |
| Real-time performance | near real-time, notify immediately when the event occurs | It depends on the polling frequency; there will be a delay. |
| Resource consumption | low Only communicate when there is an event. | High, need to send requests frequently |
| Typical scenarios | GitHub notifies the CI/CD pipeline to run; payment callbacks notify the order system. | Check the weather, retrieve the user list, submit the form |
Once you understand this difference, you have already grasped the core design philosophy of Webhook.
II. How Do Webhooks Work?
A complete Webhook interaction consists of three core steps, which can be clearly illustrated by the sequence diagram below:
Three steps explained in detail:
① Event trigger In the source application, a predefined event occurred. Common examples include:
Someone pushed code on GitHub.
Stripe 上收到了一笔付款
The user has completed registration in the new system.
② Send HTTP request The source application constructs an HTTP POST request based on the Webhook URL you preconfigured, and sends the event-related data (Payload) in the request body. The request headers usually include signature information for verification.
③ Reception and Processing After your application receives a request at the specified URL, it needs to complete the following tasks:
Verify the request signature to ensure the request actually comes from a legitimate sender.
Parse the data in the request body
Immediately submit the task to the message queue., then return 200 OK (to avoid timeouts caused by time-consuming operations)
Background Worker asynchronously consumes tasks and executes the actual business logic (updating the database, sending notifications, triggering follow-up processes, etc.)
Besides the sequence diagram, this one below System Architecture Diagram It can show the position of the Webhook in the entire system:
Core design points:
💡Why return 200 OK immediately at step three? Webhook senders usually have timeout limits (e.g., GitHub is 10 seconds) and retry mechanisms. If you perform time-consuming operations in the webhook handler (such as complex calculations or multiple database writes), once the timeout is exceeded, the sender will consider the request failed and trigger a retry, resulting in duplicate processing. The correct approach is:Return 200 immediately after verifying the signature, and delegate the actual work to an asynchronous task queue..
III. What specifications are there for Webhooks?
This is a question many beginners ask, and the answer is:Currently, there is no widely accepted mandatory global standard..
GitHub’s implementation approach, Slack’s implementation approach, DingTalk’s implementation approach… Each platform has its own way of handling details such as signature verification, retry mechanisms, and data structures. But this does not mean there is no “specification” to speak of—the industry is advancing some standardization drafts, which represent the direction of the future.
3.1 Security Authentication Specification: SWT (Secure Webhook Token)
SWT is an Internet-Draft proposed by the IETF (Internet Engineering Task Force), and its core idea is to use a kind of JWT (JSON Web Token) for special purposes to verify the legitimacy of the Webhook request.
Its key designs include:
Transmission: Mandatory use HTTPS+HTTP POST.
Token position: Put SWT in the request header
Authorization: Bearer <token>field, event data is placed in the request body.Standard Statement (Claims):
webhook.event: Event type, such aspayment.receivedwebhook.hash: Cryptographic hash of the request body content to ensure data integritywebhook.retry_count: What is the current retry number?exp,nbf,iat: Expiration time, effective time, issuance time, anti-replay attackjti: JWT ID, anti-replay
Following SWT can bring Webhook authentication to enterprise-grade security.
3.2 Process Control Specification: PCRP (Ping-Challenge-Resolve-Product)
This is another IETF draft, which focuses on The process and lifecycle management of Webhook interactions, rather than just certification.
It does this through a custom HTTP header.X-PCRP-Type to indicate the interaction stage:
ping: The sender checks whether the receiver is online and whether it is ready to receive events.challenge: The recipient initiates verification (such as CAPTCHA or Token validation) to confirm the sender's identity.resolve: The sender responds to the challenge and provides verification credentials.product: This is the final event data, which will only be sent after both ping and challenge succeed.
PCRP also defines X-PCRP-Transaction-ID(Transaction ID),X-PCRP-Nonce(anti-replay) and other headers, making Webhook interactions as reliable as the TCP three-way handshake.
Attention: SWT 和 PCRP 目前都还处于“Internet-Draft”阶段,尚未成为正式标准。但理解它们可以帮你设计出更可靠、更安全的 Webhook 系统。
IV. How to implement a Webhook using Golang?
Next, let's actually write code in Go, covering two scenarios:
Send Webhook: When an event occurs, actively call the third-party platform's Webhook URL (for example, send notifications to Slack, DingTalk, or Feishu).
Receive Webhook: Build an HTTP service to act as the receiver to handle Webhook requests sent by others.
4.1 Scenario 1: Sending a Webhook to Slack
Slack, DingTalk, and Feishu all support “Incoming Webhook”—essentially, they give you a URL, and you send a POST request to that URL to push messages to a channel or group chat.
The following is an example of sending a message using the official Slack SDK:
go
package main
import (
"fmt"
"log"
"github.com/slack-go/slack"
)
func main() {
// The URL you obtain when you create an Incoming Webhook in Slack
webhookURL := "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
// Create Webhook client
wc := slack.NewWebhook(webhookURL)
// Construct message content (supports rich layouts with Slack Block Kit)
msg := &slack.WebhookMessage{
Hello! This is a test message from a Go application.
Blocks: []slack.Block{
slack.NewSectionBlock(
slack.NewTextBlockObject("mrkdwn", "*New version deployed* :rocket:", false, false),
nil,
nil,
),
},
}
// Send
if err := wc.Send(msg); err != nil {
log.Fatalf("failed to send: %v", err)
}
fmt.Println("Message sent successfully!")
}For DingTalk and Feishu, the principle is exactly the same—you get the Webhook URL they provide and send a POST request in the JSON format they require. The only difference is in the JSON structure and signing algorithm.
💡 For the specific steps on how to obtain a Webhook URL in Slack, you can refer to another post on my blog.How to Configure Slack Incoming Webhook? From Zero to Sending Messages: The Pitfalls I Encountered.
4.2 Scenario 2: Receiving GitHub Webhooks
As the receiver, all you need to do is start an HTTP server and listen on a POST route. But The most critical step is verifying the legitimacy of the request.—Otherwise, anyone can send fake requests to your API.
The following is a complete example of handling GitHub Webhooks:
go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
// GitHub event payload structure (add more fields as needed)
type GitHubPayload struct {
Action string `json:"action"`
Repository struct {
FullName string `json:"full_name"`
} `json:"repository"`
Sender struct {
Login string `json:"login"`
} `json:"sender"`
}
// Verify GitHub signature
func verifySignature(body []byte, signatureHeader string, secret string) bool {
if signatureHeader == "" {
return false
}
// GitHub signature format: sha256=xxxxx
expectedMAC := []byte(signatureHeader)
// In practice, the part after `sha256=` needs to be parsed out; this is simplified here for illustration.
// For the complete implementation, please refer to: https://docs.github.com/zh/webhooks/securing
h := hmac.New(sha256.New, []byte(secret))
h.Write(body)
expected := hex.EncodeToString(h.Sum(nil))
// Note: In practice, a constant-time comparison function should be used to prevent timing attacks.
return hmac.Equal([]byte("sha256="+expected), []byte(signatureHeader))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// 1. Only accept POST
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 2. [Key] Verify signature
secret := os.Getenv("GITHUB_WEBHOOK_SECRET") // Read from environment variables
signature := r.Header.Get("X-Hub-Signature-256")
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
if !verifySignature(body, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// 3. Parse JSON
var payload GitHubPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// 4. Business processing (asynchronous processing is recommended here; see best practices below)
log.Printf("Received event: %s, triggered by: %s, repository: %s",
payload.Action,
payload.Sender.Login,
payload.Repository.FullName,
)
// 5. Return 200 confirmation
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
}
func main() {
http.HandleFunc("/webhook/github", webhookHandler)
log.Println("Service started at :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}V. Best Practices Checklist
Whether you implement Webhooks in Go or another language, the following points are essential to note:
| Practice Item | Description |
|---|---|
| Always use HTTPS | Production environments must use HTTPS to ensure data is not eavesdropped on or tampered with during transmission. |
| Strictly verify the signature | Do not trust any unverified requests. All mainstream webhook providers support HMAC signature verification, which is your first line of defense. |
| Return 200 OK as soon as possible | After receiving the request, first verify the signature, then immediately put the task into a message queue (such as Kafka or RabbitMQ) or an asynchronous task queue, and return 200 right away. Do not perform time-consuming operations in the Webhook handler; otherwise, the sender may time out and retry. |
| Implement idempotency | Network issues may cause the sender to retry the same Webhook request. Your processing logic must be able to identify and ignore duplicate requests, for example by checking the event ID (X-GitHub-Delivery or jti) to deduplicate. |
| Comprehensive logging and monitoring | Log all received Webhook request headers, key data, and error information. This is crucial for debugging and troubleshooting. |
VI. Frequently Asked Questions (FAQ)
Q: What is the difference between Webhook and a callback (Callback)?
A: The two are essentially the same thing—both are HTTP callbacks. But “Webhook” usually emphasizes Event-driven and automation, while “Callback” is more commonly used to describe the scenario of notifying the result after an asynchronous operation completes. In practice, these two terms are often used interchangeably.
Q: If my service goes down, will the Webhook be lost?
A: Most mainstream platforms (GitHub, Stripe, Slack, etc.) have Retry mechanism If the receiver returns a non-2xx status code or times out, they will retry multiple times according to a certain backoff strategy (usually within a few days). But if you want to be absolutely sure, you should implement message persistence and compensation mechanisms yourself.
Q: Do all webhooks use JSON?
A: JSON is currently the most mainstream choice, but it is not mandatory. Some platforms also use XML, Form Data, or even plain text. However, in the vast majority of cases, you will be dealing with JSON.
VII. Summary
| Question | Answer |
|---|---|
| What is a Webhook? | An event-driven HTTP callback in which the data source actively pushes data to you, with no need for you to poll. |
| How is it different from API? | API means you actively pull data; a webhook means someone else actively pushes data to you. |
| Is there a unified standard? | Currently, there is no mandatory international standard, but the IETF has drafts such as SWT and PCRP; at present, implementation is mainly based on each platform’s own implementation. |
| How to implement it in Go? | Sender: construct an HTTP POST request to the target URL; Receiver: set up an HTTP service, verify the signature, and process business asynchronously. |
Webhook is an extremely powerful architectural pattern that enables loosely coupled, event-driven, near-real-time communication between systems. Whether you are building CI/CD pipelines, handling payment callbacks, or integrating with various SaaS services, you will frequently work with Webhooks.
I hope this article helps you thoroughly understand Webhook.