Nahook 0.2.0

dotnet add package Nahook --version 0.2.0
                    
NuGet\Install-Package Nahook -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nahook" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nahook" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Nahook" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nahook --version 0.2.0
                    
#r "nuget: Nahook, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nahook@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nahook&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Nahook&version=0.2.0
                    
Install as a Cake Tool

Nahook .NET SDK

Official .NET SDK for the Nahook webhook platform.

Two classes, one package:

Class Purpose Auth
NahookClient Send and trigger webhook events API key (nhk_us_...)
NahookManagement Manage endpoints, event types, apps Management token (nhm_...)

Requirements

  • .NET 6+

Installation

dotnet add package Nahook

Or via the NuGet Package Manager:

Install-Package Nahook

NahookClient

Send webhooks to specific endpoints or fan-out by event type. Implements IDisposable.

Setup

using Nahook;

var client = new NahookClient("nhk_us_...");

// With options:
var client = new NahookClient("nhk_us_...", new NahookClientOptions
{
    TimeoutMs = 10_000,                  // default: 30_000ms
    Retries = 3,                         // default: 0 (no retries)
});

Configuration

The SDK automatically routes requests to the correct regional API based on your API key prefix (nhk_us_... → US, nhk_eu_... → EU, nhk_ap_... → Asia Pacific). No configuration needed.

To override the base URL (for testing or local development):

var client = new NahookClient("nhk_us_...", new NahookClientOptions
{
    BaseUrl = "http://localhost:3001",
});

For unit tests, mock the SDK client at the dependency injection boundary. For integration tests, override the base URL to point at a local server.

Advanced HTTP configuration

The SDK ships with a SocketsHttpHandler configured for keep-alive plus a 5-minute PooledConnectionLifetime — the pool cycles connections (and re-resolves DNS) instead of pinning to the IP that was resolved at process start. Without this, a long-running process can keep talking to a stale IP after a deploy / failover / DNS change. The defaults are:

Setting Value
PooledConnectionLifetime 5 minutes
PooledConnectionIdleTimeout 2 minutes
MaxConnectionsPerServer 50
AutomaticDecompression All

For most apps the defaults are enough. Two escape hatches when you want more control:

Plug in an IHttpClientFactory client. This is the recommended pattern in ASP.NET DI — let the framework manage HttpClient lifetime and attach any delegating handlers you've configured (retries, auth refresh, OpenTelemetry, etc.):

// Program.cs — your usual handler pipeline registration:
builder.Services.AddHttpClient("nahook")
    .AddHttpMessageHandler<MyRetryHandler>();

// usage
var httpClient = httpClientFactory.CreateClient("nahook");
var client = new NahookClient("nhk_us_...", new NahookClientOptions
{
    HttpClient = httpClient,
});

When HttpClient is supplied, the SDK uses it verbatim. The caller-set HttpClient.Timeout governs request timeouts (and is what NahookTimeoutException.TimeoutMs reports). The SDK will NOT dispose the supplied HttpClient on NahookClient.Dispose() and will NOT touch its DefaultRequestHeaders — the caller owns its lifecycle.

Supply only a handler. When you want the SDK to manage the HttpClient but still swap the underlying handler:

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(1),
    Proxy = new WebProxy("http://corp-proxy:8080"),
    SslOptions = new SslClientAuthenticationOptions { /* mTLS, etc. */ },
};

var client = new NahookClient("nhk_us_...", new NahookClientOptions
{
    Handler = handler,
});

The SDK wraps the handler in its own HttpClient and disposes that wrapper on Dispose(), but does NOT dispose the handler — the caller owns it. Ignored when HttpClient is also supplied. The same HttpClient and Handler options are accepted by NahookManagementOptions.

Send to a specific endpoint

var result = await client.SendAsync("ep_abc123", new
{
    OrderId = "123",
    Status = "paid",
});
// result.DeliveryId  -> "del_..."
// result.Status      -> "accepted"

Fan-out by event type

var result = await client.TriggerAsync("order.paid", new
{
    OrderId = "123",
    Status = "paid",
});
// result.EventTypeId  -> "evt_..."
// result.DeliveryIds  -> ["del_..."]
// result.Status       -> "accepted"

Batch operations

// Send to multiple endpoints (max 20 items)
var batch = await client.SendBatchAsync(new[]
{
    new SendBatchItem { EndpointId = "ep_abc", Payload = new { OrderId = "123" } },
    new SendBatchItem { EndpointId = "ep_def", Payload = new { OrderId = "456" } },
});

// Fan-out multiple event types (max 20 items)
var fanOut = await client.TriggerBatchAsync(new[]
{
    new TriggerBatchItem { EventType = "order.paid", Payload = new { OrderId = "123" } },
    new TriggerBatchItem { EventType = "order.shipped", Payload = new { OrderId = "456" } },
});

// Results: 202 (all succeed) or 207 (mixed)
foreach (var item in batch.Items)
{
    if (item.Error != null)
    {
        Console.WriteLine($"Item {item.Index} failed: {item.Error.Code}");
    }
}

Retry behavior

Retries are opt-in via the Retries constructor option. When enabled:

  • Strategy: Exponential backoff with full jitter
  • Delays: 500ms base, 10s max
  • Retryable: 5xx, 429 (respects Retry-After), network errors, timeouts
  • Non-retryable: 400, 401, 403, 404, 409, 413
  • Safe by design: Idempotency keys are always sent, making retries safe

Dispose

NahookClient implements IDisposable. Use using statements or call Dispose() when done:

using var client = new NahookClient("nhk_us_...");
await client.SendAsync("ep_abc", new { OrderId = "123" });
// HttpClient is disposed automatically

NahookManagement

Programmatically manage your Nahook workspace resources. Implements IDisposable.

Setup

using Nahook;

// Simple
var mgmt = new NahookManagement("nhm_...");

// With options
var mgmt = new NahookManagement("nhm_...", new NahookManagementOptions
{
    TimeoutMs = 30_000,                  // default
});

Endpoints

var endpoints = await mgmt.Endpoints.ListAsync("ws_abc");

var endpoint = await mgmt.Endpoints.CreateAsync("ws_abc", new CreateEndpointOptions
{
    Url = "https://example.com/webhooks",
    Description = "Production webhook",
    Type = "webhook", // "webhook" | "slack"
});

var endpoint = await mgmt.Endpoints.GetAsync("ws_abc", "ep_123");

await mgmt.Endpoints.UpdateAsync("ws_abc", "ep_123", new UpdateEndpointOptions
{
    Description = "Updated",
    IsActive = false,
});

await mgmt.Endpoints.DeleteAsync("ws_abc", "ep_123");

Event Types

var eventTypes = await mgmt.EventTypes.ListAsync("ws_abc");

var eventType = await mgmt.EventTypes.CreateAsync("ws_abc", new CreateEventTypeOptions
{
    Name = "order.paid",
    Description = "Fired when an order is paid",
});

var eventType = await mgmt.EventTypes.GetAsync("ws_abc", "evt_123");

await mgmt.EventTypes.UpdateAsync("ws_abc", "evt_123", new UpdateEventTypeOptions
{
    Description = "Updated description",
});

await mgmt.EventTypes.DeleteAsync("ws_abc", "evt_123");

Applications

var apps = await mgmt.Applications.ListAsync("ws_abc");

var app = await mgmt.Applications.CreateAsync("ws_abc", new CreateApplicationOptions
{
    Name = "Acme Corp",
    ExternalId = "acme-123",
});

var app = await mgmt.Applications.GetAsync("ws_abc", "app_123");

await mgmt.Applications.UpdateAsync("ws_abc", "app_123", new UpdateApplicationOptions
{
    Name = "Acme Inc",
});

await mgmt.Applications.DeleteAsync("ws_abc", "app_123");

// Endpoints scoped to an application
var endpoints = await mgmt.Applications.ListEndpointsAsync("ws_abc", "app_123");

var ep = await mgmt.Applications.CreateEndpointAsync("ws_abc", "app_123", new CreateEndpointOptions
{
    Url = "https://acme.com/webhooks",
});

Subscriptions

var subs = await mgmt.Subscriptions.ListAsync("ws_abc", "ep_123");

await mgmt.Subscriptions.CreateAsync("ws_abc", "ep_123", new CreateSubscriptionOptions
{
    EventTypeIds = new[] { "evt_456" },
});

await mgmt.Subscriptions.DeleteAsync("ws_abc", "ep_123", "evt_456");

Environments

var envs = await mgmt.Environments.ListAsync("ws_abc");

var env = await mgmt.Environments.CreateAsync("ws_abc", new CreateEnvironmentOptions
{
    Name = "Staging",
    Slug = "staging",
});

var env = await mgmt.Environments.GetAsync("ws_abc", "env_123");

await mgmt.Environments.UpdateAsync("ws_abc", "env_123", new UpdateEnvironmentOptions
{
    Name = "Pre-production",
});

await mgmt.Environments.DeleteAsync("ws_abc", "env_123");

Note: The Nahook.Environment model class can shadow System.Environment. If you need both in the same file, use a fully qualified name or an alias:

using NahookEnv = Nahook.Environment;

Event Type Visibility

Control which event types are visible per environment.

var visibility = await mgmt.Environments.ListEventTypeVisibilityAsync("ws_abc", "env_123");

var vis = await mgmt.Environments.SetEventTypeVisibilityAsync("ws_abc", "env_123", "evt_456", new SetVisibilityOptions
{
    Published = true,
});
// vis.EventTypeId   -> "evt_456"
// vis.EventTypeName -> "order.paid"
// vis.Published     -> true

Portal Sessions

var session = await mgmt.PortalSessions.CreateAsync("ws_abc", "app_123", new CreatePortalSessionOptions
{
    Metadata = new Dictionary<string, string> { { "userId", "user-456" } },
});
// session.Url       -> redirect end-user here
// session.Code      -> one-time exchange code
// session.ExpiresAt -> expiration timestamp

Deliveries

Read access to webhook delivery state, attempts, and (on Pro and above) the original decrypted payload.

// Paginated list, newest-first. NextCursor is an opaque encrypted token —
// pass it back verbatim, do not decode or modify it.
var page = await mgmt.Deliveries.ListAsync("ws_abc", "ep_123", new ListDeliveriesOptions
{
    Limit = 50,
});
// page.Data       -> IReadOnlyList<Delivery>
// page.NextCursor -> string? (null when there are no more pages)

if (page.NextCursor != null)
{
    var next = await mgmt.Deliveries.ListAsync("ws_abc", "ep_123", new ListDeliveriesOptions
    {
        Cursor = page.NextCursor,
    });
}

// Filter by status
var failed = await mgmt.Deliveries.ListAsync("ws_abc", "ep_123", new ListDeliveriesOptions
{
    Status = "failed",
});

// Get a single delivery's status + metadata
var delivery = await mgmt.Deliveries.GetAsync("ws_abc", "del_xyz");

// Get a delivery with its decrypted payload. The response wraps the body in
// an envelope whose Status field describes whether the payload is available,
// gated by plan ("forbidden"), still in flight ("processing"), or absent.
var withPayload = await mgmt.Deliveries.GetAsync("ws_abc", "del_xyz", new GetDeliveryOptions
{
    IncludePayload = true,
});
if (withPayload.Payload?.Status == "available")
{
    // withPayload.Payload.Data is a JsonElement? carrying the original webhook body
    Console.WriteLine(withPayload.Payload.Data);
}

// List the attempt history for a delivery
var attempts = await mgmt.Deliveries.GetAttemptsAsync("ws_abc", "del_xyz");

Error Handling

All SDK errors extend NahookException. Three specific types cover every failure mode:

using Nahook;

try
{
    await client.SendAsync("ep_abc", new { OrderId = "123" });
}
catch (NahookApiException ex)
{
    // API returned an error response
    Console.WriteLine(ex.Status);       // 404
    Console.WriteLine(ex.Code);         // "not_found"
    Console.WriteLine(ex.Message);      // "Endpoint not found"
    Console.WriteLine(ex.RetryAfter);   // seconds (on 429s)

    // Convenience checks
    if (ex.IsRetryable) { /* 5xx or 429 */ }
    if (ex.IsAuthError) { /* 401 or 403 (token_disabled) */ }
    if (ex.IsNotFound) { /* 404 */ }
    if (ex.IsRateLimited) { /* 429 */ }
    if (ex.IsValidationError) { /* 400 */ }
}
catch (NahookNetworkException ex)
{
    // Network-level failure (DNS, connection refused, etc.)
    Console.WriteLine(ex.InnerException); // original exception
}
catch (NahookTimeoutException ex)
{
    // Request exceeded configured timeout
    Console.WriteLine(ex.TimeoutMs); // timeout that was exceeded
}

Authentication

Context Token format Header
Ingestion (NahookClient) nhk_{region}_{hex} Authorization: Bearer nhk_...
Management (NahookManagement) nhm_... Authorization: Bearer nhm_...

API keys are region-aware. The SDK automatically routes to the correct regional API based on the key prefix (e.g., nhk_us_... routes to US, nhk_eu_... routes to EU).


License

MIT

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 97 5/31/2026
0.1.1 93 5/25/2026
0.1.0 98 4/27/2026