Public documentation

Documentation

Stripe-like, airy docs for building against Conduit. Learn the auth model, create endpoints, receive inbound events, inspect delivery logs, and verify every signed payload.

What is Conduit?

Getting Started

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

Authentication

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.

JWT → dashboard routesAPI key → REST API accessWebhook signature → inbound verification

Generated API keys are shown once. Store them securely. Regenerating a key invalidates the existing one immediately.

Representative endpoints

API Reference

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

POST/api/auth/register
No Auth

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

HeaderRequiredDescription
Content-TypeYesMust be application/json

Request Body

JSON
{
  "username": "danny",
  "email": "danny@example.com",
  "password": "strong-password"
}

Response

201 CREATED SUCCESS
{ "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

PUT/api/auth/api-key
JWT Required

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

HeaderRequiredDescription
AuthorizationYesBearer JWT received after login.

The API key is shown once. Calling this endpoint again replaces the existing key.

Response

200 OK Success
{
  "apiKey": "cdt_xxxxxxxxxxxxx"
}
401 Unauthorized — { "message": "not authenticated" }

CURL Example

curl -X PUT https://conduit-api.useshipyard.xyz/api/auth/api-key \
  -H "Authorization: Bearer eyJ..."

Create Endpoint

POST/api/endpoints
API Key Required

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

HeaderRequiredDescription
AuthorizationYesBearer token using a cdt_ API key.
Content-TypeYesMust be application/json.

Request Body

JSON
{
  "url": "https://your-app.com/webhooks",
  "subscribed_event": "payment.failed,order.created",
  "external_source": "stripe",
  "secret": "whsec_your_stripe_webhook_secret"
}
FieldTypeRequiredDescription
urlstringYesThe URL to deliver webhooks to.
subscribed_eventstringYesComma-separated event types.
external_sourcestringYesLabel such as github, stripe, or simulator.
secretstringNoWebhook secret. Auto-generated if omitted.

Response

201 CREATED Success
{
  "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"
  }
}
401 Unauthorized — { "message": "No API KEY" }
422 Unprocessable Entity — { "message": "Invalid inputs" }

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

POST/api/inbound/:endpointId
Webhook Signature

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

HeaderRequiredDescription
x-hub-signature-256ConditionalGitHub signature header.
stripe-signatureConditionalStripe signature header.
x-paystack-signatureConditionalPaystack signature header.
x-slack-signatureConditionalSlack signature header.
x-shopify-hmac-sha256ConditionalShopify signature header.

Path Parameters

ParameterTypeDescription
endpointIduuidThe Conduit endpoint UUID receiving the incoming webhook.

Request Body

Raw PayloadProvider Body
{
  "type": "payment.failed",
  "data": {
    "id": "evt_123",
    "amount": 5000
  }
}

Response

202 ACCEPTED Success
"Accepted"
400 Bad Request — { "message": "unsupported source" }
401 Unauthorized — { "message": "invalid signature" }
404 Not Found — { "message": "endpoint not found" }
Source detection order:
x-hub-signature-256
stripe-signature
x-paystack-signature
x-slack-signature
x-shopify-hmac-sha256

Code 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

Webhook Sources

Conduit understands the signing models of common provider ecosystems and can infer the sender by signature header presence alone.

SourceSignature HeaderAlgorithmEvent LocationReplay Protection
GitHubx-hub-signature-256HMAC-SHA256 (hex)x-github-eventNo
Stripestripe-signatureHMAC-SHA256 (hex)body.typeYes (5 min)
Paystackx-paystack-signatureHMAC-SHA512 (hex)body.eventNo
Slackx-slack-signatureHMAC-SHA256 (hex)body.event.type / body.typeYes (5 min)
Shopifyx-shopify-hmac-sha256HMAC-SHA256 (base64)x-shopify-topicNo
GitHub

GitHub

Paste repository webhook secret into Conduit

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

Stripe

Replay-safe with 5 minute window

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

Paystack

HMAC-SHA512 verification

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

Slack

Replay-safe with 5 minute window

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

Shopify

Base64 HMAC-SHA256 signature

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

Delivery & Reliability

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

AttemptBase DelayWith JitterCumulative Wait
110 seconds10s–20s~15s
230 seconds30s–60s~1 min
32 minutes2min–4min~4 min
410 minutes10min–20min~19 min
51 hour1hr–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

Security

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.

X-Conduit-SignatureX-Conduit-EventX-Conduit-Callback-IdContent-Type: application/json
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

Architecture

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.