Subscribing
Endpoint:POST /webhook
{
"callbackUrl": "https://your-server.com/webhooks/vayu",
"eventType": "Overage"
}
curl -X POST "https://connect.withvayu.com/webhook" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-api-key: $VAYU_CLIENT_ID" \
-d '{
"callbackUrl": "https://your-server.com/webhooks/vayu",
"eventType": "Overage"
}'
import { Vayu } from 'vayu-ts';
const vayu = new Vayu(process.env.VAYU_API_KEY);
await vayu.webhooks.subscribe({
callbackUrl: 'https://your-server.com/webhooks/vayu',
eventType: 'Overage',
});
from vayu_sdk import Vayu
import os
vayu = Vayu(api_key=os.environ["VAYU_API_KEY"])
vayu.webhooks.subscribe(
callback_url="https://your-server.com/webhooks/vayu",
event_type="Overage",
)
import vayu "github.com/vayucode/vayu-sdks/go"
v := vayu.NewVayu(os.Getenv("VAYU_API_KEY"))
err := v.Webhooks.Subscribe(vayu.WebhookSubscribeRequest{
CallbackUrl: "https://your-server.com/webhooks/vayu",
EventType: "Overage",
})
Available event types
| Event type | Description |
|---|---|
Overage | Customer exceeds their provisioned amount for a product |
AnonymousCustomer | Event received for an unrecognized customer alias — auto-created as anonymous |
UpcomingRenewal | Customer contract is approaching its renewal date |
InvoiceApproved | An invoice has been approved and is ready to send |
UnchargedEvents | Events exist that have not been counted against any meter |
TierCrossed | Customer usage has crossed into a new pricing tier |
CommitmentCrossed | Customer has crossed their committed usage threshold |
FinalTierExceeded | Customer usage has exceeded the final pricing tier |
InvoicePaymentStatusChanged | An invoice payment status has changed (e.g. paid, failed, overdue) |
Webhook payloads
Overage
{
"type": "Overage",
"productId": "prod_123456789",
"productName": "API Calls",
"provisionedAmount": 1000,
"consumedAmount": 1200,
"usagePercentage": 120,
"hasAccess": false,
"remainingAmount": 0,
"exceededAmount": 200
}
AnonymousCustomer
{
"type": "AnonymousCustomer",
"id": "cust_123456789",
"externalId": "ext_987654321",
"aliases": ["ext_987654321"],
"name": "Anonymous Customer"
}
UpcomingRenewal
{
"type": "UpcomingRenewal",
"customerId": "cust_123456789",
"customerName": "Acme Corp",
"contractId": "contract_abc123",
"renewalDate": "2026-02-01T00:00:00Z"
}
InvoiceApproved
{
"type": "InvoiceApproved",
"invoiceId": "inv_123456789",
"customerId": "cust_123456789",
"customerName": "Acme Corp",
"amount": 1500.00,
"currency": "USD",
"dueDate": "2026-02-15T00:00:00Z"
}
InvoicePaymentStatusChanged
{
"type": "InvoicePaymentStatusChanged",
"invoiceId": "inv_123456789",
"customerId": "cust_123456789",
"previousStatus": "PENDING",
"currentStatus": "PAID",
"amount": 1500.00,
"currency": "USD"
}
TierCrossed
{
"type": "TierCrossed",
"customerId": "cust_123456789",
"productId": "prod_123456789",
"productName": "API Calls",
"previousTier": 1,
"currentTier": 2,
"currentUsage": 10001,
"tierThreshold": 10000
}
CommitmentCrossed
{
"type": "CommitmentCrossed",
"customerId": "cust_123456789",
"contractId": "contract_abc123",
"committedAmount": 50000,
"currentUsage": 50001
}
FinalTierExceeded
{
"type": "FinalTierExceeded",
"customerId": "cust_123456789",
"productId": "prod_123456789",
"productName": "API Calls",
"finalTierThreshold": 100000,
"currentUsage": 100500
}
UnchargedEvents
{
"type": "UnchargedEvents",
"customerId": "cust_123456789",
"eventCount": 42,
"earliestEventTimestamp": "2026-01-10T08:00:00Z"
}
Handling webhook events
A minimal server that receives Vayu webhooks, routes by event type, and reads the payload:import express from 'express';
import { Vayu } from 'vayu-ts';
const vayu = new Vayu(process.env.VAYU_API_KEY);
const app = express();
app.use(express.json());
// Register the webhook (run once)
// await vayu.webhooks.subscribe({
// callbackUrl: 'https://your-server.com/webhooks/vayu',
// eventType: 'Overage',
// });
app.post('/webhooks/vayu', (req, res) => {
const event = req.body;
switch (event.type) {
case 'Overage':
console.log(
`${event.productName}: ${event.consumedAmount}/${event.provisionedAmount} used ` +
`(${event.exceededAmount} over limit)`
);
break;
case 'InvoiceApproved':
console.log(`Invoice ${event.invoiceId} for ${event.customerName}: $${event.amount} ${event.currency}`);
break;
case 'TierCrossed':
console.log(`${event.productName}: customer moved from tier ${event.previousTier} → ${event.currentTier}`);
break;
case 'AnonymousCustomer':
console.log(`New anonymous customer created: ${event.externalId}`);
break;
default:
console.log(`Unhandled event: ${event.type}`);
}
res.sendStatus(200);
});
app.listen(3000, () => console.log('Listening on :3000'));
from flask import Flask, request
from vayu_sdk import Vayu
import os
vayu = Vayu(api_key=os.environ["VAYU_API_KEY"])
app = Flask(__name__)
# Register the webhook (run once)
# vayu.webhooks.subscribe(
# callback_url="https://your-server.com/webhooks/vayu",
# event_type="Overage",
# )
@app.route('/webhooks/vayu', methods=['POST'])
def handle_webhook():
event = request.get_json()
if event['type'] == 'Overage':
print(
f"{event['productName']}: {event['consumedAmount']}/{event['provisionedAmount']} used "
f"({event['exceededAmount']} over limit)"
)
elif event['type'] == 'InvoiceApproved':
print(f"Invoice {event['invoiceId']} for {event['customerName']}: ${event['amount']} {event['currency']}")
elif event['type'] == 'TierCrossed':
print(f"{event['productName']}: customer moved from tier {event['previousTier']} → {event['currentTier']}")
elif event['type'] == 'AnonymousCustomer':
print(f"New anonymous customer created: {event['externalId']}")
else:
print(f"Unhandled event: {event['type']}")
return '', 200
if __name__ == '__main__':
app.run(port=3000)
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
vayu "github.com/vayucode/vayu-sdks/go"
)
// Register the webhook (run once)
// v := vayu.NewVayu(os.Getenv("VAYU_API_KEY"))
// v.Webhooks.Subscribe(vayu.WebhookSubscribeRequest{
// CallbackUrl: "https://your-server.com/webhooks/vayu",
// EventType: "Overage",
// })
func main() {
http.HandleFunc("/webhooks/vayu", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var event map[string]interface{}
json.Unmarshal(body, &event)
switch event["type"] {
case "Overage":
fmt.Printf("%s: %.0f/%.0f used (%.0f over limit)\n",
event["productName"], event["consumedAmount"],
event["provisionedAmount"], event["exceededAmount"])
case "InvoiceApproved":
fmt.Printf("Invoice %s for %s: $%.2f %s\n",
event["invoiceId"], event["customerName"],
event["amount"], event["currency"])
case "TierCrossed":
fmt.Printf("%s: customer moved from tier %.0f → %.0f\n",
event["productName"], event["previousTier"], event["currentTier"])
case "AnonymousCustomer":
fmt.Printf("New anonymous customer created: %s\n", event["externalId"])
default:
fmt.Printf("Unhandled event: %s\n", event["type"])
}
w.WriteHeader(http.StatusOK)
})
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}
In production, always verify the webhook signature before processing the payload.
Webhook security
All Vayu webhook requests include headers for signature verification:| Header | Description |
|---|---|
X-Timestamp | Unix timestamp (seconds) of when the request was sent |
X-Signature | Base64-encoded RSA-SHA256 signature of timestamp.JSON(payload) |
X-Signature-Version | Signature scheme version (currently v1) |
${timestamp}.${JSON.stringify(payload)}
Only HTTPS callback URLs are supported.
Verifying signatures
The TypeScript SDK provides a built-in helper. Built-in verification for the Python and Go SDKs is coming soon — in the meantime, verify manually using Vayu’s public key.import { Vayu } from 'vayu-ts';
const vayu = new Vayu(process.env.VAYU_API_KEY);
app.post('/webhooks', async (req, res) => {
const payload = JSON.stringify(req.body);
const timestamp = Number(req.headers['x-timestamp']);
const signature = String(req.headers['x-signature']);
const isValid = vayu.webhooks.verifyWebhookSignature({
payload,
timestamp,
signature,
tolerance: 300, // optional — reject if older than 5 minutes (default)
});
if (!isValid) {
return res.sendStatus(401);
}
const event = req.body;
console.log('Received webhook:', event.type);
res.sendStatus(200);
});
import json, time, base64, math
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from flask import Flask, request
# Load Vayu's public key (provided during onboarding)
with open("vayu-public.pem", "rb") as f:
public_key = serialization.load_pem_public_key(f.read())
TOLERANCE = 300 # 5 minutes
app = Flask(__name__)
@app.route('/webhooks', methods=['POST'])
def webhooks():
payload = request.get_data(as_text=True)
timestamp = int(request.headers.get('X-Timestamp', '0'))
signature = request.headers.get('X-Signature', '')
# Reject stale webhooks
if abs(time.time() - timestamp) > TOLERANCE:
return '', 401
# Reconstruct the signed message
message = f"{timestamp}.{json.dumps(json.loads(payload))}"
try:
public_key.verify(
base64.b64decode(signature),
message.encode(),
padding.PKCS1v15(),
hashes.SHA256(),
)
except Exception:
return '', 401
event = json.loads(payload)
print('Received webhook:', event['type'])
return '', 200
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"math"
"net/http"
"os"
"strconv"
"time"
)
// Load Vayu's public key (provided during onboarding)
var publicKey *rsa.PublicKey
func init() {
keyData, _ := os.ReadFile("vayu-public.pem")
block, _ := pem.Decode(keyData)
pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
publicKey = pub.(*rsa.PublicKey)
}
const tolerance = 300 // 5 minutes
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
timestamp, _ := strconv.ParseInt(r.Header.Get("X-Timestamp"), 10, 64)
signature := r.Header.Get("X-Signature")
// Reject stale webhooks
if math.Abs(float64(time.Now().Unix()-timestamp)) > tolerance {
http.Error(w, "stale webhook", http.StatusUnauthorized)
return
}
// Reconstruct the signed message
message := fmt.Sprintf("%d.%s", timestamp, string(body))
hash := sha256.Sum256([]byte(message))
sigBytes, _ := base64.StdEncoding.DecodeString(signature)
err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], sigBytes)
if err != nil {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
var event map[string]interface{}
json.Unmarshal(body, &event)
fmt.Println("Received webhook:", event["type"])
w.WriteHeader(http.StatusOK)
}
