BUILD WITHOUT THE DELIVERY HEADACHE

Every event.
Accounted for.

From device alerts and task updates to notification triggers and bookings: send an event with one API request. Hookstride signs outgoing requests, retries eligible failures, and records delivery attempts. Receivers must handle duplicates.

✓ HMAC signing✓ Automatic retries✓ Event replay
BASE URLhttps://api.hookstride.com
BEFORE YOU BUILD

What is Hookstride, and when should you use it?

One transport and retry layer for application events—not a product tied to payments, framework, or industry.

WHAT

Webhook delivery infrastructure

Hookstride accepts custom events such as sensor.temperature.high, message.created or task.updated, stores them, and delivers signed HTTP POST requests to your registered destinations. Eligible failures are retried and attempts are recorded.

WHY

Receivers are not always available

When a downstream service times out or returns a transient error, you should not need to build your own queue, retry scheduler, and delivery dashboard.

Your backendCreates the eventHookstridePersists, signs and retriesReceiverVerifies and processes

Your responsibility: decide when events happen, create or choose receiving endpoints, verify delivery signatures, and process events idempotently. Hookstride's responsibility: reliably attempt delivery and show you the outcome. Read the plain-English explanation →

APPLICATION EVENTS

Use Hookstride for events across your systems

Your systems decide what constitutes an event and how to process it.

IOT & MONITORING

sensor.temperature.high

Deliver a device-gateway alert to your monitoring backend; MQTT-to-HTTP bridging remains outside Hookstride.

TEAM COLLABORATION

task.updated

Notify a task automation or audit service after a committed change; your app still manages live collaboration.

NOTIFICATIONS

notification.requested

Signal an email, SMS, or push worker; Hookstride is not the message sender.

AI & AUTOMATION

document.processed

Trigger downstream processing when a service finishes a job.

LOGISTICS

vehicle.arrived

Update a tracking service when a vehicle reaches a checkpoint.

BOOKINGS

booking.confirmed

Notify scheduling or operations after a reservation changes state.

WHEN TO USE IT

Delivery when an event matters

Hookstride handles transport, not your application's business logic. Your receiver remains responsible for verifying signatures and processing duplicate deliveries safely.

YOUR BACKEND

Defines the event

Commit the business action, then submit an event to Hookstride.

HOOKSTRIDE

Delivers and retries

Store the event, sign HTTP requests, monitor attempts and retry eligible failures.

GET STARTED

Integrate in three steps

The core flow for sending your first signed webhook. You own the receiver and its business logic.

02
↗

Add a destination

Create or choose a POST receiver (not your homepage), then enter its public URL.

Add your URL
03
⌘

Generate a key & send

Copy your API key, then send your first event with one request.

Get your key
DEVELOPER TOOLS

CLI & interactive console

Inspect projects and deliveries from your terminal.

Install

uv tool install hookstride-cli

CLI: run a command

printf 'Control token: '
read -rs HOOKSTRIDE_CONTROL_TOKEN
printf '\n'
export HOOKSTRIDE_CONTROL_TOKEN
hookstride --env production projects list
hookstride --env production projects use PROJECT_UUID
hookstride --env production events list

Interactive console

hookstride --env production console
auth login
show projects
use PROJECT_UUID
show events
exit

Use a Control API token from your dashboard, not a webhook API key. The CLI defaults to development; --env production selects production. The console prompt shows [production] or [production:project-id]. Install from PyPI after the first CLI package release.

SENDING

Send an event from your backend

Your server calls Hookstride after the business action succeeds. Keep credentials out of the browser and mobile app.

Before you send

Create a destination under Destinations. Save the returned endpoint UUID and signing secret. Then create an API key under API keys.

Set HOOKSTRIDE_API_KEY and HOOKSTRIDE_ENDPOINT_ID in your server's environment. The endpoint UUID identifies which destination receives this event; it is different from your API key.

cURL: create a receiver destination

curl -X POST 'https://api.hookstride.com/api/v1/endpoints/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Task notifications","url":"https://your-app.example.com/webhooks/hookstride"}'

cURL: send a booking-confirmed event

curl -X POST 'https://api.hookstride.com/api/v1/events/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: booking-BK-1042-confirmed' \
  -d '{
    "endpoint": "DESTINATION_UUID",
    "event_type": "booking.confirmed",
    "payload": {
      "booking_id": "BK-1042",
      "property_id": "PROP-21",
      "status": "confirmed"
    }
  }'

cURL: task collaboration event

curl -X POST 'https://api.hookstride.com/api/v1/events/' -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" -H 'Content-Type: application/json' -H 'Idempotency-Key: task-T-104-updated' -d '{"endpoint":"DESTINATION_UUID","event_type":"task.updated","payload":{"task_id":"T-104","status":"done"}}'

cURL: IoT gateway alert

curl -X POST 'https://api.hookstride.com/api/v1/events/' -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" -H 'Content-Type: application/json' -H 'Idempotency-Key: alert-SENSOR-7-20261103' -d '{"endpoint":"DESTINATION_UUID","event_type":"sensor.temperature.high","payload":{"device_id":"SENSOR-7","temperature_c":42.8}}'

Python / Django backend

import os
import requests

def notify_booking_confirmed(booking):
    response = requests.post(
        "https://api.hookstride.com/api/v1/events/",
        headers={
            "Authorization": "Bearer " + os.environ["HOOKSTRIDE_API_KEY"],
            "Idempotency-Key": f"booking-{booking.pk}-confirmed",
        },
        json={
            "endpoint": os.environ["HOOKSTRIDE_ENDPOINT_ID"],
            "event_type": "booking.confirmed",
            "payload": {
                "booking_id": str(booking.pk),
                "property_id": str(booking.property_id),
                "status": "confirmed",
            },
        },
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

JavaScript / Node.js backend

const response = await fetch("https://api.hookstride.com/api/v1/events/", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.HOOKSTRIDE_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "booking-BK-1042-confirmed",
  },
  body: JSON.stringify({
    endpoint: process.env.HOOKSTRIDE_ENDPOINT_ID,
    event_type: "booking.confirmed",
    payload: { booking_id: "BK-1042", property_id: "PROP-21", status: "confirmed" },
  }),
});
if (!response.ok) throw new Error("Hookstride rejected the event");
console.log(await response.json());

Call these examples only after the business state is committed. For critical events, use a durable outbox in your own database so a temporary network failure while calling Hookstride can be retried safely.

RECEIVING

Build the destination that gets the webhook

Hookstride sends signed HTTP POST requests to a URL you control. The destination is a receiving route—not your homepage or Hookstride's API.

Example: an online store receiver

If your website is shop.example, you might implement https://api.shop.example/webhooks/hookstride/ in your backend. This is an illustrative URL: Hookstride cannot create the route in your application.

When registering a destination, save its one-time signing secret on the receiving server as HOOKSTRIDE_SIGNING_SECRET. This secret is not your project API key.

1. Django model: durable, duplicate-safe receipt

# models.py — run makemigrations and migrate after adding
from django.db import models

class HookstrideReceipt(models.Model):
    event_id = models.UUIDField(primary_key=True)
    event_type = models.CharField(max_length=160)
    envelope = models.JSONField()
    processed = models.BooleanField(default=False)
    received_at = models.DateTimeField(auto_now_add=True)

2. Django view: verify before accepting

# views.py
import hashlib
import hmac
import json
import os
import time
import uuid

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from .models import HookstrideReceipt

@csrf_exempt  # External webhooks use HMAC, not browser CSRF tokens.
@require_POST
def receive_hookstride(request):
    raw = request.body
    timestamp = request.headers.get("X-Webhook-Timestamp", "")
    signature = request.headers.get("X-Webhook-Signature", "")
    event_id = request.headers.get("X-Webhook-Id", "")

    try:
        event_uuid = uuid.UUID(event_id)
        seconds = int(timestamp)
        if abs(time.time() - seconds) > 300:
            return HttpResponse(status=401)
    except (ValueError, TypeError):
        return HttpResponse(status=401)

    secret = os.environ["HOOKSTRIDE_SIGNING_SECRET"].encode()
    digest = hmac.new(
        secret, timestamp.encode() + b"." + raw, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(signature, "v1=" + digest):
        return HttpResponse(status=401)

    try:
        envelope = json.loads(raw)
        if envelope["id"] != str(event_uuid):
            return HttpResponse(status=400)
        HookstrideReceipt.objects.get_or_create(
            event_id=event_uuid,
            defaults={
                "event_type": envelope["type"],
                "envelope": envelope,
            },
        )
    except (ValueError, KeyError, TypeError):
        return HttpResponse(status=400)
    # A separate worker should process unprocessed receipts idempotently.
    return JsonResponse({"received": True})

3. Django URL

# urls.py
from django.urls import path
from .views import receive_hookstride

urlpatterns = [
    path("api/v1/webhooks/hookstride/", receive_hookstride),
]

This example durably accepts a verified event; it does not send tickets or emails by itself. Add a worker to process pending receipts and mark them processed after your business operation succeeds. Make that operation idempotent, recover unprocessed receipts, and return HTTP 5xx when you cannot safely persist the event.

FastAPI / Python receiver example

This verifies a delivery but still needs a durable, unique-ID receipt store and background processor before production use.

import hashlib
import hmac
import json
import os
import time
import uuid
from fastapi import FastAPI, HTTPException, Request

app = FastAPI()

@app.post("/webhooks/hookstride")
async def hookstride_receiver(request: Request):
    body = await request.body()
    timestamp = request.headers.get("x-webhook-timestamp", "")
    signature = request.headers.get("x-webhook-signature", "")
    event_id = request.headers.get("x-webhook-id", "")
    try:
        uuid.UUID(event_id)
        if abs(time.time() - int(timestamp)) > 300:
            raise ValueError("Expired signature")
    except ValueError:
        raise HTTPException(status_code=401, detail="Invalid timestamp or ID")
    digest = hmac.new(
        os.environ["HOOKSTRIDE_SIGNING_SECRET"].encode(),
        timestamp.encode() + b"." + body,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(signature, "v1=" + digest):
        raise HTTPException(status_code=401, detail="Invalid signature")
    try:
        envelope = json.loads(body)
        if not isinstance(envelope, dict) or envelope.get("id") != event_id:
            raise ValueError("Webhook ID does not match signed payload")
    except (ValueError, UnicodeError, TypeError):
        raise HTTPException(status_code=400, detail="Invalid event body")
    # TODO: durably store envelope["id"] as UNIQUE before returning 2xx,
    # and process pending receipts idempotently with your worker.
    return {"received": True}
Express / Node.js receiver example

Verify the raw body and signature before processing. Add a durable receipt store for production.

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();
app.post("/webhooks/hookstride",
  express.raw({ type: "application/json" }), async (req, res) => {
    const timestamp = req.get("X-Webhook-Timestamp") || "";
    const signature = req.get("X-Webhook-Signature") || "";
    const eventId = req.get("X-Webhook-Id") || "";
    const seconds = Number(timestamp);
    if (!eventId || !Number.isInteger(seconds) ||
        Math.abs(Date.now() / 1000 - seconds) > 300) {
      return res.sendStatus(401);
    }
    const expected = "v1=" + createHmac(
      "sha256", process.env.HOOKSTRIDE_SIGNING_SECRET
    ).update(timestamp + ".").update(req.body).digest("hex");
    const actual = Buffer.from(signature);
    const correct = Buffer.from(expected);
    if (actual.length !== correct.length ||
        !timingSafeEqual(actual, correct)) return res.sendStatus(401);

    let envelope;
    try {
      envelope = JSON.parse(req.body.toString("utf8"));
    } catch {
      return res.sendStatus(400);
    }
    if (!envelope || typeof envelope !== "object" ||
        Array.isArray(envelope) || envelope.id !== eventId) {
      return res.sendStatus(400);
    }
    // TODO: durably store envelope.id with a unique constraint
    // before returning 2xx; process with an idempotent worker.
    return res.status(200).json({ received: true });
  }
);
Laravel / PHP receiver example

Validate the timestamp, raw body and HMAC using the destination signing secret.

// Laravel route handler (inside your controller)
$raw = $request->getContent();
$timestamp = $request->header('X-Webhook-Timestamp', '');
$provided = $request->header('X-Webhook-Signature', '');
$eventId = $request->header('X-Webhook-Id', '');

if (!ctype_digit($timestamp) ||
    abs(time() - (int) $timestamp) > 300 ||
    empty($eventId)) {
    abort(401);
}
$expected = 'v1=' . hash_hmac(
    'sha256',
    $timestamp . '.' . $raw,
    config('services.hookstride.signing_secret')
);
if (!hash_equals($expected, $provided)) abort(401);

$envelope = json_decode($raw, true);
if (!is_array($envelope) || !isset($envelope['id']) ||
    !is_string($envelope['id']) || $envelope['id'] !== $eventId) {
    abort(400);
}
// TODO: durably store $envelope['id'] as UNIQUE before returning 2xx,
// and process with a queued, idempotent job.
return response()->json(['received' => true]);
CREDENTIALS & PROVIDERS

Which keys go where?

Do not mix API authentication keys, provider webhook verification secrets, and Hookstride's own keys. Here is where each value belongs.

ValueUse and storage location
Hookstride project API key whrk_...Generate in Dashboard → API keys. Store in the sending backend as HOOKSTRIDE_API_KEY, used for POST /api/v1/events/.
Hookstride destination signing secretShown once when you create a destination. Store in the receiving backend as HOOKSTRIDE_SIGNING_SECRET. It verifies requests signed by Hookstride.
Stripe publishable pk_... and secret API key sk_...Publishable key: your checkout frontend. Secret API key: your backend's secret store. Neither is required for outgoing Hookstride events.
Stripe webhook signing secret whsec_...Configure on the optional Stripe incoming source once its generated gateway URL is registered with Stripe; otherwise keep this secret in your own Stripe receiver.
Paystack public and secret keysPublic key in the checkout frontend; secret key in the backend. Paystack verifies webhooks with its full-access API secret key, so a Hookstride incoming connection requires security review.
PayPal client ID, client secret and webhook IDUse the backend for confidential credentials. PayPal is not a native Hookstride incoming source yet.
Didit API key and signing secretKeep these in your backend and verified Didit receiver. Didit is not a native Hookstride incoming source yet.

Using Hookstride to send your own events (available now)

Example: ABC Store at shop.example accepts payments using Stripe. The store keeps its Stripe API key on its own backend, verifies the payment, then submits order.paid to Hookstride using only its Hookstride project API key. Hookstride delivers the event to the fulfillment endpoint, which verifies the destination signing secret.

No Stripe, PayPal, Didit or Paystack key is entered into Hookstride for this outgoing workflow. See sending events and receiving deliveries.

Optional: forwarding external Stripe or Paystack webhooks

When the inbound gateway is enabled, open Dashboard → Connections and choose a provider and destination. For Stripe, register the generated inactive source URL with Stripe, obtain its whsec_... secret, save it in Hookstride and activate the source only after a route is configured.

STRIPE

Use the whsec_... secret

Do not paste the Stripe sk_... API key into the signing-secret field. Configure the verified webhook destination and an active route before enabling the source.

PAYSTACK

Understand the access required

Paystack signs webhooks with the full API secret key. Use the incoming connection only after security review, or verify Paystack events in your own backend and send outgoing Hookstride events.

What about PayPal or Didit?

Hookstride has native incoming verifiers for Stripe, Paystack, GitHub and custom HMAC only. Do not select Custom to bypass another provider's signature verification. Receive and verify those webhooks in your own backend first.

AUTHENTICATION

One API key for your app

Generate a project key in your dashboard and pass it with each request.

Authorization header

Authorization: Bearer $API_KEY

The dashboard handles account setup, projects, and key generation. Your backend only needs this key to send events.

For advanced account management, /control/v1/ uses a separate short-lived control token. Owners and administrators must complete MFA before receiving privileged credentials.

INBOUND GATEWAY

One URL. Multiple destinations.

Receive signed events from external services and route them to the right application.

01

Connect your sender

Create a source in the dashboard and copy its gateway URL into the sender's webhook settings.

Connect a source
02

Choose destinations

Add any number of destinations. Route by event type, JSON field or a reference registered by your backend.

Manage destinations

Gateway URL format

https://api.hookstride.com/ingest/v1/SOURCE_UUID/

Custom HMAC, GitHub, Stripe and Paystack verifiers are available. Other senders require a signed adapter.

Trusted reference routing

Sharing one sender account across several applications? Register each operation's reference from its owning backend before the sender creates it. When a signed webhook arrives, the gateway routes it only to the destination bound to that reference—not to every application.

Use a project API key or a limited registration key for the trusted registration request. An untrusted application ID in a webhook payload cannot select the destination.

Register an application reference →
RELIABILITY

Delivery & security

Every event is persisted before asynchronous delivery, with a signed request and audit trail.

AUTHENTICITY

Verify every payload

Outbound requests include X-Webhook-Id, X-Webhook-Event, X-Webhook-Timestamp and X-Webhook-Signature.

v1 = HMAC_SHA256(secret, timestamp + "." + raw_body)

Use the exact raw request body and compare signatures in constant time. Check timestamp tolerance and deduplicate by webhook ID.

RECOVERY

Automatic backoff

Network failures and transient HTTP responses are retried. The default retry schedule follows increasing delays:

30s2m10m1h6hFailed

Events are delivered at least once. Your receiver should be idempotent.

API REFERENCE · v1

The API reference

Everything you need to integrate, manage, and troubleshoot.

42 endpoints

Webhook delivery API

Project API key authentication.

GET/api/v1/project/Project
200 response

Project

Get the project associated with your project API key.

Project key
Request body
No JSON request body required.
Example response 200
{
  "id": "PROJECT_UUID",
  "name": "Production",
  "slug": "production",
  "monthly_event_quota": 1000
}
curl -X GET 'https://api.hookstride.com/api/v1/project/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/usage/Usage
200 response

Usage

Return this month's event quota and delivery counters.

Project key
Request body
No JSON request body required.
Example response 200
{
  "period_start": "2026-10-01",
  "events_ingested": 125,
  "delivery_attempts": 142,
  "events_delivered": 120,
  "events_failed": 3,
  "event_quota": 1000,
  "events_remaining": 875
}
curl -X GET 'https://api.hookstride.com/api/v1/usage/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/endpoints/List endpoints
200 response

List endpoints

List paginated delivery destinations for the current project.

Project key
Request body
No JSON request body required.
Example response 200
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "ENDPOINT_UUID",
      "name": "Production receiver",
      "url": "https://example.com/webhooks",
      "is_active": true
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/endpoints/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/endpoints/Create endpoint
201 response

Create endpoint

Register a public HTTPS webhook destination. The signing secret appears only in this response.

Project key
Request body
{
  "name": "Production receiver",
  "url": "https://example.com/webhooks"
}
Example response 201
{
  "id": "ENDPOINT_UUID",
  "name": "Production receiver",
  "url": "https://example.com/webhooks",
  "is_active": true,
  "signing_secret": "SECRET_SAVE_ONCE"
}
curl -X POST 'https://api.hookstride.com/api/v1/endpoints/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production receiver","url":"https://example.com/webhooks"}'
GET/api/v1/endpoints/{id}/Endpoint details
200 response

Endpoint details

Inspect one destination belonging to the authenticated project.

Project key
Request body
No JSON request body required.
Example response 200
{
  "id": "ENDPOINT_UUID",
  "name": "Production receiver",
  "url": "https://example.com/webhooks",
  "is_active": true
}
curl -X GET 'https://api.hookstride.com/api/v1/endpoints/{id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
PATCH/api/v1/endpoints/{id}/Update endpoint
200 response

Update endpoint

Change a destination's name, URL, or active status.

Project key
Request body
{
  "name": "Updated receiver",
  "is_active": true
}
Example response 200
{
  "id": "ENDPOINT_UUID",
  "name": "Updated receiver",
  "url": "https://example.com/webhooks",
  "is_active": true
}
curl -X PATCH 'https://api.hookstride.com/api/v1/endpoints/{id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Updated receiver","is_active":true}'
POST/api/v1/endpoints/{id}/rotate-secret/Rotate signing secret
200 response

Rotate signing secret

Issue a new endpoint signing secret. Rotation takes effect immediately.

Project key
Request body
No JSON request body required.
Example response 200
{
  "endpoint": "ENDPOINT_UUID",
  "signing_secret": "NEW_SECRET_SAVE_ONCE"
}
curl -X POST 'https://api.hookstride.com/api/v1/endpoints/{id}/rotate-secret/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/events/List events
200 response

List events

List paginated events. Filter by status, event_type, or endpoint UUID.

Project key
Request body
No JSON request body required.
Example response 200
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "EVENT_UUID",
      "event_type": "payment.completed",
      "status": "delivered",
      "attempt_count": 1
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/events/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/events/Send an event
201 response

Send an event

Persist and enqueue a signed webhook. Send an optional Idempotency-Key header to prevent duplicates.

Project key
Request body
{
  "endpoint": "ENDPOINT_UUID",
  "event_type": "payment.completed",
  "payload": {
    "payment_id": "pay_123",
    "amount": 250,
    "currency": "GHS"
  }
}
Example response 201
{
  "id": "EVENT_UUID",
  "endpoint": "ENDPOINT_UUID",
  "event_type": "payment.completed",
  "status": "pending",
  "attempt_count": 0,
  "attempts": []
}
curl -X POST 'https://api.hookstride.com/api/v1/events/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endpoint":"ENDPOINT_UUID","event_type":"payment.completed","payload":{"payment_id":"pay_123","amount":250,"currency":"GHS"}}'
GET/api/v1/events/{id}/Event details
200 response

Event details

Retrieve an event with delivery attempts, errors, and timing information.

Project key
Request body
No JSON request body required.
Example response 200
{
  "id": "EVENT_UUID",
  "event_type": "payment.completed",
  "status": "delivered",
  "attempt_count": 1,
  "attempts": [
    {
      "attempt_number": 1,
      "status_code": 200,
      "duration_ms": 110
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/events/{id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/events/{id}/replay/Replay event
201 response

Replay event

Requeue a delivered or permanently failed event as a new event linked to its original.

Project key
Request body
No JSON request body required.
Example response 201
{
  "id": "NEW_EVENT_UUID",
  "replayed_from": "EVENT_UUID",
  "status": "pending",
  "attempts": []
}
curl -X POST 'https://api.hookstride.com/api/v1/events/{id}/replay/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/stats/Delivery statistics
200 response

Delivery statistics

Retrieve totals, successful deliveries, failures, pending events, and success rate.

Project key
Request body
No JSON request body required.
Example response 200
{
  "project": "PROJECT_UUID",
  "events": {
    "total": 100,
    "delivered": 92,
    "failed": 3,
    "pending": 5,
    "success_rate": 92
  }
}
curl -X GET 'https://api.hookstride.com/api/v1/stats/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"

Incoming webhook gateway

Optional signature verification and routing.

GET/api/v1/sources/List connected sources
200 response

List connected sources

Retrieve your project's external and custom webhook sources.

Project key
Request body
No JSON request body required.
Example response 200
{
  "results": [
    {
      "id": "SOURCE_UUID",
      "name": "Shared provider",
      "provider": "custom",
      "routing": "rules"
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/sources/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/sources/Connect a webhook source
201 response

Connect a webhook source

Register a signed sender. Copy the returned ingress URL into its webhook settings.

Project key
Request body
{
  "name": "Shared sender",
  "provider": "custom",
  "routing": "rules"
}
Example response 201
{
  "id": "SOURCE_UUID",
  "name": "Shared sender",
  "ingest_url": "https://example.com/ingest/v1/SOURCE_UUID/",
  "signing_secret": "ONE_TIME_SECRET"
}
curl -X POST 'https://api.hookstride.com/api/v1/sources/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Shared sender","provider":"custom","routing":"rules"}'
PATCH/api/v1/sources/{id}/Update source
200 response

Update source

Pause or rename an existing incoming source.

Project key
Request body
{
  "is_active": false
}
Example response 200
{
  "id": "SOURCE_UUID",
  "name": "Shared sender",
  "is_active": false
}
curl -X PATCH 'https://api.hookstride.com/api/v1/sources/{id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active":false}'
POST/api/v1/sources/{id}/rotate-secret/Rotate source secret
200 response

Rotate source secret

Replace the stored source signing secret; sender updates must be coordinated.

Project key
Request body
{}
Example response 200
{
  "id": "SOURCE_UUID",
  "rotated": true,
  "signing_secret": "ONE_TIME_SECRET_FOR_CUSTOM"
}
curl -X POST 'https://api.hookstride.com/api/v1/sources/{id}/rotate-secret/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
POST/api/v1/routes/Create event routing rule
201 response

Create event routing rule

Forward signed events to a project-owned destination matching an event type and optional field.

Project key
Request body
{
  "source": "SOURCE_UUID",
  "endpoint": "ENDPOINT_UUID",
  "event_pattern": "order.*"
}
Example response 201
{
  "id": "RULE_UUID",
  "source": "SOURCE_UUID",
  "endpoint": "ENDPOINT_UUID"
}
curl -X POST 'https://api.hookstride.com/api/v1/routes/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"SOURCE_UUID","endpoint":"ENDPOINT_UUID","event_pattern":"order.*"}'
GET/api/v1/routes/List event routing rules
200 response

List event routing rules

Review subscriptions controlling inbound fan-out.

Project key
Request body
No JSON request body required.
Example response 200
{
  "results": []
}
curl -X GET 'https://api.hookstride.com/api/v1/routes/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/reference-bindings/Register trusted reference
201 response

Register trusted reference

Bind an external reference to one destination before the external event is created.

Project key
Request body
{
  "source": "SOURCE_UUID",
  "endpoint": "ENDPOINT_UUID",
  "reference": "ORDER-123"
}
Example response 201
{
  "id": "BINDING_UUID",
  "reference": "ORDER-123"
}
curl -X POST 'https://api.hookstride.com/api/v1/reference-bindings/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"SOURCE_UUID","endpoint":"ENDPOINT_UUID","reference":"ORDER-123"}'
GET/api/v1/reference-bindings/List trusted references
200 response

List trusted references

View pre-registered event ownership bindings.

Project key
Request body
No JSON request body required.
Example response 200
{
  "results": []
}
curl -X GET 'https://api.hookstride.com/api/v1/reference-bindings/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/inbound-events/Incoming event history
200 response

Incoming event history

Inspect accepted upstream events without exposing their payload by default.

Project key
Request body
No JSON request body required.
Example response 200
{
  "results": [
    {
      "id": "MESSAGE_UUID",
      "event_type": "order.created",
      "route_count": 1
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/inbound-events/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/inbound-events/{id}/redispatch/Redispatch an incoming event
200 response

Redispatch an incoming event

Send a stored upstream event to newly matching destinations without duplicating prior deliveries.

Project key
Request body
No JSON request body required.
Example response 200
{
  "id": "MESSAGE_UUID",
  "routed": 1,
  "duplicate": false
}
curl -X POST 'https://api.hookstride.com/api/v1/inbound-events/{id}/redispatch/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
GET/api/v1/ingress-stats/Incoming event statistics
200 response

Incoming event statistics

Monitor received, unmatched and delivered events in your project.

Project key
Request body
No JSON request body required.
Example response 200
{
  "sources": 1,
  "received": 120,
  "unrouted": 2,
  "outbound_deliveries": 118
}
curl -X GET 'https://api.hookstride.com/api/v1/ingress-stats/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/reference-registration-keys/Create a limited registration key
201 response

Create a limited registration key

Issue a one-time key for exactly one external source and one destination. Store it only on that app's backend.

Project key
Request body
{
  "source": "SOURCE_UUID",
  "endpoint": "DESTINATION_UUID",
  "name": "Voting app"
}
Example response 201
{
  "id": "KEY_UUID",
  "masked_key": "whrr_...abcd",
  "token": "ONE_TIME_RESTRICTED_KEY",
  "expires_at": "2027-01-28T00:00:00Z"
}
curl -X POST 'https://api.hookstride.com/api/v1/reference-registration-keys/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"SOURCE_UUID","endpoint":"DESTINATION_UUID","name":"Voting app"}'
GET/api/v1/reference-registration-keys/List limited registration keys
200 response

List limited registration keys

View masked, expiring credentials for each app without exposing token values.

Project key
Request body
No JSON request body required.
Example response 200
{
  "results": [
    {
      "id": "KEY_UUID",
      "name": "Voting app",
      "masked_key": "whrr_...abcd"
    }
  ]
}
curl -X GET 'https://api.hookstride.com/api/v1/reference-registration-keys/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/api/v1/reference-registration-keys/{id}/revoke/Revoke a limited registration key
200 response

Revoke a limited registration key

Immediately disable one app's registration access without affecting other destinations.

Project key
Request body
No JSON request body required.
Example response 200
{
  "id": "KEY_UUID",
  "is_active": false
}
curl -X POST 'https://api.hookstride.com/api/v1/reference-registration-keys/{id}/revoke/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
POST/ingest/v1/register-reference/Register an application reference
201 response

Register an application reference

Use the app's limited token to claim its own order, transaction or subscription reference before the provider emits an event. No source or destination ID needed.

Registration key
Request body
{
  "reference": "ORDER-APP-123"
}
Example response 201
{
  "id": "BINDING_UUID",
  "reference": "ORDER-APP-123"
}
curl -X POST 'https://api.hookstride.com/ingest/v1/register-reference/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reference":"ORDER-APP-123"}'
GET/api/v1/destinations/{id}/delivery/Inspect destination delivery format
200 response

Inspect destination delivery format

See how a destination receives JSON and which authentication mode is configured. Secrets are never returned.

Project key
Request body
No JSON request body required.
Example response 200
{
  "endpoint": "DESTINATION_UUID",
  "payload_format": "envelope",
  "field_map": {},
  "auth_mode": "signed",
  "credential_configured": false
}
curl -X GET 'https://api.hookstride.com/api/v1/destinations/{id}/delivery/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY"
PUT/api/v1/destinations/{id}/delivery/Configure destination delivery
200 response

Configure destination delivery

Choose standard envelope, original JSON or bounded field mapping; optionally add an encrypted Bearer token or X-Api-Key. Every delivery still carries the destination's HMAC signature.

Project key
Request body
{
  "payload_format": "mapped",
  "field_map": {
    "transaction.reference": "payload.data.reference",
    "transaction.event": "event.type",
    "app": {
      "const": "my-application"
    }
  },
  "auth_mode": "bearer",
  "credential": "DESTINATION_TOKEN"
}
Example response 200
{
  "endpoint": "DESTINATION_UUID",
  "payload_format": "mapped",
  "field_map": {
    "transaction.reference": "payload.data.reference",
    "transaction.event": "event.type",
    "app": {
      "const": "my-application"
    }
  },
  "auth_mode": "bearer",
  "credential_configured": true
}
curl -X PUT 'https://api.hookstride.com/api/v1/destinations/{id}/delivery/' \
  -H "Authorization: Bearer $HOOKSTRIDE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload_format":"mapped","field_map":{"transaction.reference":"payload.data.reference","transaction.event":"event.type","app":{"const":"my-application"}},"auth_mode":"bearer","credential":"DESTINATION_TOKEN"}'

Account Control API

Workspace and project administration with a separate control token.

POST/control/v1/auth/email/start/Request an email sign-in code
200 response

Request an email sign-in code

Send a single-use, 10-minute code. Optionally set a workspace name when registering; passwords are not supported.

Public
Request body
{
  "email": "dev@example.com",
  "workspace_name": "Acme"
}
Example response 200
{
  "challenge": "EMAIL_CHALLENGE_UUID",
  "expires_in": 600
}
curl -X POST 'https://api.hookstride.com/control/v1/auth/email/start/' \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com","workspace_name":"Acme"}'
POST/control/v1/auth/email/verify/Verify a one-time email code
202 response

Verify a one-time email code

Complete email verification. Privileged members must additionally complete MFA; the Next.js flow receives a 202 challenge, never a privileged token from email verification alone.

Public
Request body
{
  "challenge": "EMAIL_CHALLENGE_UUID",
  "code": "ABCD2345"
}
Example response 202
{
  "requires_mfa": true,
  "enrollment_required": true,
  "challenge": "MFA_CHALLENGE",
  "expires_in": 600
}
curl -X POST 'https://api.hookstride.com/control/v1/auth/email/verify/' \
  -H "Content-Type: application/json" \
  -d '{"challenge":"EMAIL_CHALLENGE_UUID","code":"ABCD2345"}'
POST/control/v1/auth/google/verify/Verify a Google sign-in identity token
202 response

Verify a Google sign-in identity token

Verify a Google OIDC ID token using Google's JWKS and the configured client audience and one-time nonce. Privileged users receive an MFA challenge instead of a bearer token.

Public
Request body
{
  "id_token": "GOOGLE_OIDC_ID_TOKEN",
  "nonce": "OAUTH_ONE_TIME_NONCE"
}
Example response 202
{
  "requires_mfa": true,
  "enrollment_required": false,
  "challenge": "MFA_CHALLENGE",
  "expires_in": 600
}
curl -X POST 'https://api.hookstride.com/control/v1/auth/google/verify/' \
  -H "Content-Type: application/json" \
  -d '{"id_token":"GOOGLE_OIDC_ID_TOKEN","nonce":"OAUTH_ONE_TIME_NONCE"}'
POST/control/v1/auth/logout/Sign out
204 response

Sign out

Revoke the current control token. Returns no response body.

Control token
Request body
No JSON request body required.
Example response 204
204 No Content
curl -X POST 'https://api.hookstride.com/control/v1/auth/logout/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
GET/control/v1/me/Your profile
200 response

Your profile

Retrieve the authenticated user and their account memberships.

Control token
Request body
No JSON request body required.
Example response 200
{
  "user": {
    "id": 1,
    "email": "dev@example.com"
  },
  "memberships": [
    {
      "account_name": "Acme",
      "role": "owner"
    }
  ]
}
curl -X GET 'https://api.hookstride.com/control/v1/me/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
GET/control/v1/projects/List projects
200 response

List projects

List projects accessible to your account.

Control token
Request body
No JSON request body required.
Example response 200
[
  {
    "id": "PROJECT_UUID",
    "name": "Production",
    "role": "owner",
    "monthly_event_quota": 1000
  }
]
curl -X GET 'https://api.hookstride.com/control/v1/projects/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
POST/control/v1/projects/Create project
201 response

Create project

Create an additional project within an account where you have admin access.

Control token
Request body
{
  "account_id": "ACCOUNT_UUID",
  "name": "Staging",
  "slug": "staging"
}
Example response 201
{
  "id": "PROJECT_UUID",
  "name": "Staging",
  "slug": "staging",
  "role": "owner"
}
curl -X POST 'https://api.hookstride.com/control/v1/projects/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"account_id":"ACCOUNT_UUID","name":"Staging","slug":"staging"}'
GET/control/v1/projects/{project_id}/Project details
200 response

Project details

Read the project configuration and subscription summary.

Control token
Request body
No JSON request body required.
Example response 200
{
  "id": "PROJECT_UUID",
  "name": "Production",
  "monthly_event_quota": 1000,
  "subscription": {
    "plan_code": "free",
    "status": "active"
  }
}
curl -X GET 'https://api.hookstride.com/control/v1/projects/{project_id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
GET/control/v1/projects/{project_id}/usage/Project usage history
200 response

Project usage history

Get current usage, up to 12 months of history, and subscription details.

Control token
Request body
No JSON request body required.
Example response 200
{
  "current": {
    "events_ingested": 125,
    "event_quota": 1000
  },
  "history": [],
  "subscription": {
    "plan_code": "free",
    "status": "active"
  }
}
curl -X GET 'https://api.hookstride.com/control/v1/projects/{project_id}/usage/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
GET/control/v1/projects/{project_id}/subscription/Subscription details
200 response

Subscription details

Read the project's current subscription status.

Control token
Request body
No JSON request body required.
Example response 200
{
  "provider": "direct",
  "plan_code": "free",
  "status": "active"
}
curl -X GET 'https://api.hookstride.com/control/v1/projects/{project_id}/subscription/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
GET/control/v1/projects/{project_id}/api-keys/List API keys
200 response

List API keys

List masked keys for a project. The raw secret is never returned again.

Control token
Request body
No JSON request body required.
Example response 200
[
  {
    "id": "KEY_UUID",
    "name": "Production server",
    "masked_key": "whrk_••••",
    "is_active": true
  }
]
curl -X GET 'https://api.hookstride.com/control/v1/projects/{project_id}/api-keys/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
POST/control/v1/projects/{project_id}/api-keys/Issue API key
201 response

Issue API key

Create a project-scoped key. Store the raw key securely: it is shown only at issuance.

Control token
Request body
{
  "name": "Production server",
  "expires_days": 90
}
Example response 201
{
  "api_key": {
    "id": "KEY_UUID",
    "name": "Production server"
  },
  "key": "whrk_••••"
}
curl -X POST 'https://api.hookstride.com/control/v1/projects/{project_id}/api-keys/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production server","expires_days":90}'
DELETE/control/v1/projects/{project_id}/api-keys/{key_id}/Revoke API key
204 response

Revoke API key

Immediately revoke a key. Revoked credentials cannot access the data API.

Control token
Request body
No JSON request body required.
Example response 204
204 No Content
curl -X DELETE 'https://api.hookstride.com/control/v1/projects/{project_id}/api-keys/{key_id}/' \
  -H "Authorization: Bearer $HOOKSTRIDE_CONTROL_TOKEN"
ERRORS

Errors & limits

Predictable HTTP responses help integrations recover safely.

StatusMeaningAction
400Invalid request or parametersCorrect the body, filter or URL.
401Invalid or expired credentialsCheck your bearer token or API key.
403Insufficient permissionsUse an appropriately authorized role.
404Unknown or inaccessible resourceCheck the ID and project scope.
409Idempotency conflictUse new idempotency data or replay a terminal event.
429Quota or rate limit exceededWait for reset or review plan usage.

Examples are illustrative and never contain real API keys.Read the signing and retry guide →