Skip to content

TypeScript SDK

Sparrow provides generated TypeScript client stubs for Connect-RPC. This is the recommended approach for both browser and Node.js applications — no gRPC proxy required, just standard HTTP.

Terminal window
npm install @bufbuild/protobuf @connectrpc/connect @connectrpc/connect-web

Then copy the generated stubs into your project:

Terminal window
cp -r client/js/connect/proto/ src/lib/sparrow/
  1. Create a transport and client

    import { createClient } from "@connectrpc/connect";
    import { createConnectTransport } from "@connectrpc/connect-web";
    import { WebhookService, EventService } from "./lib/sparrow/webhook_pb";
    const transport = createConnectTransport({
    baseUrl: "http://localhost:8080",
    });
    const webhookClient = createClient(WebhookService, transport);
    const eventClient = createClient(EventService, transport);
  2. Register a webhook and push an event

    // Register a webhook
    const webhookResp = await webhookClient.registerWebhook({
    namespace: "default",
    url: "https://example.com/webhook",
    events: ["order.created"],
    active: true,
    });
    console.log("Webhook ID:", webhookResp.webhookId);
    // Push an event
    const pushResp = await eventClient.pushEvent({
    namespace: "default",
    event: "order.created",
    payload: {
    order_id: "ord_456",
    total: 149.99,
    },
    ttlSeconds: 3600,
    });
    console.log("Event ID:", pushResp.eventId);

If the Sparrow server has SPARROW_API_KEY set, add the key via a transport interceptor:

import type { Interceptor } from "@connectrpc/connect";
const apiKeyInterceptor: Interceptor = (next) => async (req) => {
req.header.set("X-API-Key", "your-api-key");
return next(req);
};
const transport = createConnectTransport({
baseUrl: "http://localhost:8080",
interceptors: [apiKeyInterceptor],
});
import { SubscriptionService } from "./lib/sparrow/webhook_pb";
const subscriptionClient = createClient(SubscriptionService, transport);
// Create a subscription that only receives premium events
await subscriptionClient.createSubscription({
webhookId: webhookResp.webhookId,
eventName: "order.created",
namespace: "default",
active: true,
labelFilters: {
plan: "premium",
},
});
// Push an event with labels
await eventClient.pushEvent({
namespace: "default",
event: "order.created",
payload: { order_id: "ord_789", total: 299.99 },
ttlSeconds: 3600,
labels: {
plan: "premium",
region: "us-east-1",
},
});
import { DeliveryService } from "./lib/sparrow/webhook_pb";
const deliveryClient = createClient(DeliveryService, transport);
const deliveries = await deliveryClient.listDeliveries({
webhookId: webhookResp.webhookId,
namespace: "default",
limit: 10,
});
for (const d of deliveries.deliveries) {
console.log(`Delivery ${d.id}: status=${d.status} attempts=${d.attemptCount}`);
}
import { HealthService } from "./lib/sparrow/webhook_pb";
const healthClient = createClient(HealthService, transport);
const health = await healthClient.getWebhookHealth({
webhookId: webhookResp.webhookId,
namespace: "default",
});
console.log(`Health: ${health.healthStatus}, success_rate=${health.successRate}%`);

Connect-RPC uses standard HTTP error codes mapped to gRPC status codes:

import { ConnectError, Code } from "@connectrpc/connect";
try {
await webhookClient.registerWebhook({
namespace: "default",
url: "https://example.com/webhook",
events: ["order.created"],
active: true,
});
} catch (err) {
if (err instanceof ConnectError) {
switch (err.code) {
case Code.NotFound:
console.error("Resource not found");
break;
case Code.AlreadyExists:
console.error("Already exists");
break;
case Code.InvalidArgument:
console.error("Invalid input:", err.message);
break;
default:
console.error("RPC error:", err.code, err.message);
}
}
}
Service DescriptorDescription
WebhookServiceRegister, update, pause/resume webhooks
EventServicePush events, register event types, re-push
SubscriptionServiceManage webhook-event subscriptions
DeliveryServiceQuery delivery history, retry deliveries
HealthServiceWebhook health metrics and summaries