What is Conduit?
Conduit is a source-agnostic webhook relay that receives events from external platforms, verifies their signatures, stores delivery state, and reliably pushes signed payloads to your endpoint.
The product separates ingestion from delivery, so your downstream API can recover on its own timeline while Conduit keeps retrying with backoff, jitter, and full inspection logs.
Quick Start
1.Create your account
Register, then sign in to the dashboard with JWT auth.
2.Generate an API key
Use your API key for programmatic endpoint management.
3.Create an endpoint
Provide a destination URL, source label, and event subscriptions.
4.Send a test event
Use the simulator or point a real provider at your inbound URL.
Core concepts:endpoint is your destination URL,callback is a delivery attempt, andsecret is the shared key.
JWT and API keys
Conduit uses two auth models. JWTs power the dashboard experience and user session. API keys are for programmatic access to endpoint management, delivery logs, and simulator endpoints.
Generated API keys are shown once. Store them securely. Regenerating a key invalidates the existing one immediately.
Representative endpoints
This first documentation view emphasizes structure and clarity: top-level docs plus four representative endpoints that demonstrate unauthenticated auth flows, JWT-protected key creation, API-key endpoint management, and webhook ingestion.
Register
Creates a new Conduit user account. This endpoint is used before dashboard access and returns a simple success message once the user record is stored.
Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | Must be application/json |
Request Body
{
"username": "danny",
"email": "danny@example.com",
"password": "strong-password"
}Response
{ "message": "user created" }CURL Example
curl -X POST https://conduit-api.useshipyard.xyz/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "danny",
"email": "danny@example.com",
"password": "strong-password"
}'Generate API Key
Generates or replaces the active API key for the authenticated user. The key is returned once and should be stored securely by the client.
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer JWT received after login. |
The API key is shown once. Calling this endpoint again replaces the existing key.
Response
{
"apiKey": "cdt_xxxxxxxxxxxxx"
}CURL Example
curl -X PUT https://conduit-api.useshipyard.xyz/api/auth/api-key \
-H "Authorization: Bearer eyJ..."Create Endpoint
Creates a new webhook destination for the authenticated user. You provide the destination URL, a source label, subscribed events, and optionally a webhook secret.
Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer token using a cdt_ API key. |
| Content-Type | Yes | Must be application/json. |
Request Body
{
"url": "https://your-app.com/webhooks",
"subscribed_event": "payment.failed,order.created",
"external_source": "stripe",
"secret": "whsec_your_stripe_webhook_secret"
}| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | The URL to deliver webhooks to. |
| subscribed_event | string | Yes | Comma-separated event types. |
| external_source | string | Yes | Label such as github, stripe, or simulator. |
| secret | string | No | Webhook secret. Auto-generated if omitted. |
Response
{
"endpoint": {
"id": "14109de8-03d6-4267-b0cd-ec8627241ba0",
"endpointPath": "https://your-app.com/webhooks",
"status": "active",
"subscribedEvent": ["payment.failed", "order.created"],
"externalSource": "stripe",
"secret": "whsec_your_stripe_webhook_secret"
}
}If secret is omitted, a 32-byte random secret is generated automatically and returned once. Store it securely for downstream signature verification.
CURL Example
curl -X POST https://conduit-api.useshipyard.xyz/api/endpoints \
-H "Authorization: Bearer cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks",
"subscribed_event": "payment.failed,order.created",
"external_source": "stripe",
"secret": "whsec_your_stripe_webhook_secret"
}'Receive Webhook
Accepts raw webhook payloads from external services. Conduit auto-detects the source by checking which provider-specific signature header is present, then validates the payload against the stored secret before queuing delivery.
Headers
| Header | Required | Description |
|---|---|---|
| x-hub-signature-256 | Conditional | GitHub signature header. |
| stripe-signature | Conditional | Stripe signature header. |
| x-paystack-signature | Conditional | Paystack signature header. |
| x-slack-signature | Conditional | Slack signature header. |
| x-shopify-hmac-sha256 | Conditional | Shopify signature header. |
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| endpointId | uuid | The Conduit endpoint UUID receiving the incoming webhook. |
Request Body
{
"type": "payment.failed",
"data": {
"id": "evt_123",
"amount": 5000
}
}Response
"Accepted"
x-hub-signature-256stripe-signaturex-paystack-signaturex-slack-signaturex-shopify-hmac-sha256Code Example
curl -X POST https://conduit-api.useshipyard.xyz/api/inbound/14109de8-03d6-4267-b0cd-ec8627241ba0 \
-H "stripe-signature: t=1492774577,v1=6a9582..." \
-d '{
"type": "payment.failed",
"data": {
"id": "evt_123",
"amount": 5000
}
}'Supported providers
Conduit understands the signing models of common provider ecosystems and can infer the sender by signature header presence alone.
| Source | Signature Header | Algorithm | Event Location | Replay Protection |
|---|---|---|---|---|
| GitHub | x-hub-signature-256 | HMAC-SHA256 (hex) | x-github-event | No |
| Stripe | stripe-signature | HMAC-SHA256 (hex) | body.type | Yes (5 min) |
| Paystack | x-paystack-signature | HMAC-SHA512 (hex) | body.event | No |
| Slack | x-slack-signature | HMAC-SHA256 (hex) | body.event.type / body.type | Yes (5 min) |
| Shopify | x-shopify-hmac-sha256 | HMAC-SHA256 (base64) | x-shopify-topic | No |
GitHub
Find the signing secret in your repository or organization webhook settings. Use the generated Conduit inbound URL as the webhook URL and map GitHub events like push or pull_request into your subscribed event list.
Stripe
Copy the Stripe signing secret from the Developers → Webhooks section, paste it into the Conduit endpoint secret field, and subscribe to events such as payment.failed or invoice.paid.
Paystack
Use your dashboard secret key-derived signature flow and point Paystack to the Conduit inbound URL. Choose subscribed events that mirror the incoming body.event names.
Slack
Configure Slack Events API to POST to your Conduit inbound URL using the signing secret from your app settings. Slack events are identified in body.event.type or body.type depending on the event type.
Shopify
Copy your webhook signing secret from the Shopify Admin API credentials and configure your webhook URL to point to Conduit. Map Shopify topics like orders/create or products/update from the x-shopify-topic header.
How delivery works
Inbound events are verified, persisted, queued, consumed, delivered, retried, and eventually marked dead if recovery never succeeds within the retry policy.
1.Inbound request accepted
Event arrives at /api/inbound/:endpointId.
2.Signature verified
Raw payload is checked against the endpoint's stored secret.
3.Pending callback stored
Conduit writes a callback record in PostgreSQL.
4.Job queued in Redis
BullMQ picks the delivery up asynchronously.
5.Worker delivers
Signed POST request is sent to your endpoint URL.
6.Retries or completion
2xx becomes delivered, otherwise retries continue until dead.
Retry schedule
| Attempt | Base Delay | With Jitter | Cumulative Wait |
|---|---|---|---|
| 1 | 10 seconds | 10s–20s | ~15s |
| 2 | 30 seconds | 30s–60s | ~1 min |
| 3 | 2 minutes | 2min–4min | ~4 min |
| 4 | 10 minutes | 10min–20min | ~19 min |
| 5 | 1 hour | 1hr–2hr | ~1.5 hr |
Conduit uses full jitter (AWS recommended): delay = baseDelay + random(0, baseDelay). This avoids thundering herd retries against recovering endpoints.
After 5 failed attempts, a delivery moves to dead status. Dead deliveries remain queryable and can be manually replayed through the API or dashboard.
Verifying Conduit signatures
Every outbound webhook Conduit delivers includes X-Conduit-Signature. Compute an HMAC-SHA256 of the raw request body using your endpoint secret and compare it with a timing-safe function.
const crypto = require('crypto')
function verifyConduitSignature(req, secret) {
const signature = req.headers['x-conduit-signature'];
const hmac = crypto.createHmac('sha256', secret);
const expected = 'cdtsig_sha256=' + hmac.update(req.rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}Always use timing-safe comparison. Never compare signatures with == or ===. This prevents timing attacks where an attacker can brute-force the signature.
Producer-consumer pattern
Conduit keeps ingestion and delivery independent. The API process receives and persists data. The worker process consumes queued jobs later and handles retries without blocking the ingest path.
API server
Verifies source signatures and writes pending callback records.
Redis queue
Acts as the durable queue between ingestion and delivery workers.
Worker
Fetches callback + endpoint state and attempts signed delivery.
Write-ahead persistence
Callbacks are stored before delivery so failures never erase visibility.