Webhooks
Webhooks allow FLUX to notify your application in real time when important events occur, such as successful filings, failures, or status updates. Instead of polling the API, your system receives HTTP callbacks as events happen.
How Webhooks Work
- 1You register a webhook URL in your application.
- 2FLUX sends an HTTP POST request to that URL when an event occurs.
- 3Your server validates the request and processes the event.
- 4You return a 2xx response to acknowledge receipt.
When to Use Webhooks
Use webhooks when:
- You need real-time updates on filing status
- You want to track success or failure without polling
- You are building asynchronous or event-driven workflows
Supported Events
TaxStreem emits webhook events for key lifecycle changes across all products:
vat.filing.success{
"eventType": "vat.filing.success",
"eventPayload": {
"scheduleSheet": "https://storage.googleapis.com/bucket/partner123/schedule.xlsx",
"filingReport": "https://storage.googleapis.com/bucket/partner123/report.png",
"filingId": "fil_abc123",
"partnerId": "partner_456",
"status": true,
"taxType": "VAT",
"paymentDetails": {
"paymentReference": "PAY-9023490",
"paymentURL": "https://pay.taxstreem.com/PAY-9023490",
"paymentAmount": "15000",
"taxType": "VAT"
}
}
}vat.filing.no-pending{
"eventType": "vat.filing.no-pending",
"eventPayload": {
"filingId": "fil_abc123",
"status": false,
"message": "No Pending Filing for this period",
"error_type": "",
"details": "No Pending Filing for this period"
}
}vat.filing.already-filed{
"eventType": "vat.filing.already-filed",
"eventPayload": {
"filingId": "fil_abc123",
"status": false,
"message": "Already filed",
"error_type": "",
"details": "Already filed"
}
}vat.filing.failed{
"eventType": "vat.filing.failed",
"eventPayload": {
"filingId": "fil_abc123",
"status": false,
"message": "TIN validation failed",
"error_type": "VAT_FILING_ERROR",
"details": "Taxpayer TIN does not exist in NRS"
}
}wht.filing.success{
"eventType": "wht.filing.success",
"eventPayload": {
"scheduleSheet": "https://storage.googleapis.com/bucket/partner123/schedule.xlsx",
"filingReport": "https://storage.googleapis.com/bucket/partner123/report.png",
"filingId": "fil_abc123",
"partnerId": "partner_456",
"status": true,
"taxType": "WHT",
"paymentDetails": {
"paymentReference": "PAY-9023490",
"paymentURL": "https://pay.taxstreem.com/PAY-9023490",
"paymentAmount": "15000",
"taxType": "WHT"
}
}
}wht.filing.failed{
"event": "wht.filing.failed",
"data": {
"filingId": "fil_wht002",
"status": "failed",
"error": "Rate validation failed",
"beneficiary": "Vendor Inc"
}
}Webhook Request
All webhook events are delivered as HTTP POST requests with a JSON payload.
Headers
| Header | Description |
|---|---|
| Content-Type | application/json |
| x-taxstreem-signature | Signature used to verify the webhook payload authenticity. |
Verify Event Origin
Since your webhook URL is publicly available, you need to verify that events originate from TaxStreem and not a bad actor. There are two ways to ensure events to your webhook URL are from TaxStreem:
- Signature validation (Recommended)
- IP whitelisting
Signature Validation
Events sent from TaxStreem carry the x-taxstreem-signature header. The value of this header is a HMAC SHA512 signature of the event payload signed using your secret key.
⚠️ Verifying the header signature should be done before processing the event.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hash = crypto
.createHmac('sha512', secret)
.update(JSON.stringify(payload))
.digest('hex');
return hash === signature;
}
// Usage in your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['x-taxstreem-signature'];
const isValid = verifyWebhookSignature(req.body, signature, process.env.TAXSTREEM_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the event
res.status(200).send('OK');
});import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret):
hash_obj = hmac.new(
secret.encode('utf-8'),
json.dumps(payload).encode('utf-8'),
hashlib.sha512
)
return hash_obj.hexdigest() == signature
# Usage in Flask
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('x-taxstreem-signature')
is_valid = verify_webhook_signature(
request.json,
signature,
os.environ['TAXSTREEM_SECRET']
)
if not is_valid:
return 'Invalid signature', 401
# Process the event
return 'OK', 200package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"encoding/json"
)
func verifyWebhookSignature(payload interface{}, signature, secret string) bool {
payloadBytes, _ := json.Marshal(payload)
h := hmac.New(sha512.New, []byte(secret))
h.Write(payloadBytes)
computedHash := hex.EncodeToString(h.Sum(nil))
return computedHash == signature
}
// Usage in HTTP handler
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("x-taxstreem-signature")
var payload map[string]interface{}
json.NewDecoder(r.Body).Decode(&payload)
if !verifyWebhookSignature(payload, signature, os.Getenv("TAXSTREEM_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process the event
w.WriteHeader(http.StatusOK)
}Security Best Practices
- Always validate the signature before processing the payload
- Reject requests with invalid or missing signatures (return 401)
- Use HTTPS endpoints only
- Store your secret key securely (environment variables, secrets manager)
- Never log the secret key or signature in plain text
Retry & Failure Handling
If your endpoint fails to respond with a 2xx status code, FLUX will retry delivery automatically following an exponential backoff strategy.
Webhook Response
Your server should respond with any 2xx HTTP status code to acknowledge successful receipt.
{
"event": "vat.filing.success",
"timestamp": "2024-03-20T10:00:00Z",
"data": {
"id": "evt_283838",
"status": "success",
"reference": "TX-239293"
}
}