TaxStreem Logo
DOCS/DEV

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

  1. 1You register a webhook URL in your application.
  2. 2FLUX sends an HTTP POST request to that URL when an event occurs.
  3. 3Your server validates the request and processes the event.
  4. 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
vat.filing.success
Response (200 OK)
{
  "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
vat.filing.no-pending
Response (200 OK)
{
  "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
vat.filing.already-filed
Response (200 OK)
{
  "eventType": "vat.filing.already-filed",
  "eventPayload": {
    "filingId": "fil_abc123",
    "status": false,
    "message": "Already filed",
    "error_type": "",
    "details": "Already filed"
  }
}
VAT Filing Failed
vat.filing.failed
Response (200 OK)
{
  "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
wht.filing.success
Response (200 OK)
{
  "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
wht.filing.failed
Response (200 OK)
{
  "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

HeaderDescription
Content-Typeapplication/json
x-taxstreem-signatureSignature 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.

NODE.JSSignature Verification
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');
});
PYTHONSignature Verification
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', 200
GOLANGSignature Verification
package 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.

Note: Your webhook handlers must be idempotent as events may be delivered more than once.

Webhook Response

Your server should respond with any 2xx HTTP status code to acknowledge successful receipt.

HTTP/1.1 200 OK
Example Payload
{
  "event": "vat.filing.success",
  "timestamp": "2024-03-20T10:00:00Z",
  "data": {
    "id": "evt_283838",
    "status": "success",
    "reference": "TX-239293"
  }
}