> This page is for version v6.0 (default).
> For other versions, use one of these documentation indexes:
> - v6.0 (default): https://docs.zip.tax/v-6-0/llms.txt
> - v5.0: https://docs.zip.tax/v-5-0/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.zip.tax/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.zip.tax/_mcp/server.

# Best practices

A few habits make webhook integrations robust. Follow these when building your
handler.

## Acknowledge fast, then do the work

Return a `2xx` status **immediately** to acknowledge receipt, then do any heavy
work (fetching rates, updating your database) asynchronously. Slow handlers risk
timing out before they acknowledge.

## Treat the payload as a trigger

After a `rate.updated` event, call the [Ziptax rate API](../rest-api/overview)
to fetch the authoritative updated rates for the indicated authority. Do not
treat the webhook body as the source of truth for rate values. It only tells
you **which** authority changed.

## Make handlers idempotent

Be prepared to receive the same event more than once. Design your handler so
that processing a duplicate event is harmless (for example, key your work off
the authority in the payload rather than blindly appending records).

## Verify authenticity

Use your [signing secret](verify) to confirm requests are genuinely from Ziptax
once the verification scheme is published. Until then, keep your endpoint URL
private and rely on re-fetching authoritative data from the API.

## Use HTTPS

Serve every production endpoint over `https://`. This protects the payload in
transit and is required for a secure integration.

## A minimal receiver

The same handler is shown below in Node.js, Python, and Go. Use the tabs to
switch languages. Each one acknowledges quickly with a `2xx`, then treats the
event as a trigger to re-fetch rates for the indicated authority. These are
illustrative starting points; adapt them to your stack.

#### Node.js

```javascript
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/ziptax", (req, res) => {
  // TODO: verify the X-Signature header using your Ziptax signing secret (whsec_...)
  // See "Verify & secure deliveries" for a full example.

  const { event, data } = req.body;

  if (event === "rate.updated") {
    const { locality, code } = data.rateUpdateDetail;
    // A rate changed for this authority. Fetch the latest rates from the Ziptax API.
    console.log(`Rate updated for ${locality} ${code}`);
  }

  // Acknowledge quickly.
  res.sendStatus(200);
});

app.listen(3000);
```

#### Python

```python
from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/ziptax")
def ziptax_webhook():
    # TODO: verify the X-Signature header using your Ziptax signing secret (whsec_...)
    # See "Verify & secure deliveries" for a full example.

    payload = request.get_json()

    if payload.get("event") == "rate.updated":
        detail = payload["data"]["rateUpdateDetail"]
        # A rate changed for this authority. Fetch the latest rates from the Ziptax API.
        print(f"Rate updated for {detail['locality']} {detail['code']}")

    # Acknowledge quickly.
    return "", 200
```

#### Go

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type webhookEvent struct {
	Event string `json:"event"`
	Data  struct {
		RateUpdateDetail struct {
			Locality string `json:"locality"`
			Code     string `json:"code"`
		} `json:"rateUpdateDetail"`
	} `json:"data"`
}

func main() {
	http.HandleFunc("/webhooks/ziptax", func(w http.ResponseWriter, r *http.Request) {
		// TODO: verify the X-Signature header using your Ziptax signing secret (whsec_...)
		// See "Verify & secure deliveries" for a full example.

		var evt webhookEvent
		if err := json.NewDecoder(r.Body).Decode(&evt); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		if evt.Event == "rate.updated" {
			d := evt.Data.RateUpdateDetail
			// A rate changed for this authority. Fetch the latest rates from the Ziptax API.
			log.Printf("Rate updated for %s %s", d.Locality, d.Code)
		}

		// Acknowledge quickly.
		w.WriteHeader(http.StatusOK)
	})

	log.Fatal(http.ListenAndServe(":3000", nil))
}
```

> **Info**
>
> This minimal receiver leaves signature verification as a `TODO` to stay short.
> In production, verify the `X-Signature` header before trusting a delivery. See
> [Verify & secure deliveries](verify) for a full example.