Releases: jmarbutt/InngestDotNet
v1.4.4
v1.4.3
Fix: Throw error when INNGEST_EVENT_KEY missing in production
Previously, SendEventsWithIdsAsync would silently fail and return an empty array when the event key was not configured in production mode. This made debugging difficult as events appeared to "send" but the response was just { "ids": [] }.
Now the SDK throws a clear InvalidOperationException explaining that INNGEST_EVENT_KEY is required for production event sending.
This fixes issues where fan-out patterns (cron → per-item workers) would silently fail because the parent job couldn't send events.
v1.4.2
Bug Fix
Fixed: PUT requests no longer require signature verification
Problem
The SDK was incorrectly requiring signature verification for PUT requests (sync/introspection), causing {"error":"Invalid signature"} when manually testing endpoints via curl or browser.
Solution
Moved signature verification to only apply to POST requests (function calls). According to the Inngest SDK spec:
- PUT (sync) - Does NOT require signatures
- GET (introspection) - Does NOT require signatures
- POST (function calls) - Requires valid signature in production
This allows manual testing of sync endpoints while maintaining security for actual function invocations.
Full Changelog
v1.4.1
What's New
SDK Parity Features for Node.js Migration (#10)
Five critical features to enable seamless migration from the Node.js SDK:
- Multi-constraint concurrency - Support both per-key serialization and global caps simultaneously
- Idempotency TTL/Period support - Explicit duration control (24h, 1h, etc.) instead of implicit Inngest defaults
- Retry/attempt helpers -
IsFinalAttemptproperty andMaxAttemptstracking in function context - Structured logging scopes -
function_id,run_id,event_id, andattemptautomatically included in all logs - First-class onFailure handlers - Companion functions triggered on
inngest/function.failedevents via[OnFailure]attribute
Security & Auth Hardening (#9)
- Fixed
INNGEST_DEVparsing sofalse/0no longer incorrectly disables signature checks - Signature verification now properly enforced only for PUT/POST requests
- Returns 401 on invalid signatures with future-timestamp guard
- Pinned .NET SDK version via
global.jsonfor consistent builds
Other Improvements
- Cleaned all remaining build warnings
- Aligned CI to .NET 9
- Removed user IDE settings from repo
Stats
- 23 files changed
- 1,445 additions, 64 deletions
- 20 new tests added (147 total)
Full Changelog: v1.4.0...v1.4.1
v1.4.0 - Flow Control Attributes
New Features
This release adds three new attributes for production-grade function configuration, specifically designed for payment processing and other critical workflows where data integrity is paramount.
[Throttle] Attribute
QUEUES events at a rate limit (unlike [RateLimit] which DROPS events). Critical for payment processing where losing events is unacceptable.
// Limit to 20 executions per minute per customer, queuing excess events
[Throttle(20, "1m", Key = "event.data.customerId")][Idempotency] Attribute
Prevents duplicate processing using a CEL expression key. Ensures only one execution per unique key (e.g., contribution ID).
// One receipt per contribution - prevents duplicate emails on retry
[Idempotency("event.data.contributionId")][Timeout] Attribute
Cancels hanging functions to prevent queue buildup. Supports both Start (queue timeout) and Finish (execution timeout).
// Cancel if function takes longer than 30 seconds to complete
[Timeout(Finish = "30s")]
// Cancel if queued longer than 1 minute or runs longer than 2 minutes
[Timeout(Start = "1m", Finish = "2m")]Complete Example
[InngestFunction("payment-processor", Name = "Process Payment Webhook")]
[EventTrigger("payment/received")]
[Throttle(20, "1m", Key = "event.data.customerId")] // Queue, don't drop
[Concurrency(1, Key = "event.data.paymentId")] // Serialize per payment
[Idempotency("event.data.paymentId")] // One execution per payment
[Timeout(Finish = "30s")] // Cancel if hanging
public class PaymentProcessorFunction : IInngestFunction
{
// ...
}Why These Features Matter
These features are required for migrating Node.js Inngest handlers to .NET, particularly for payment processing that cannot tolerate:
- Duplicate charges from webhook retries → solved by
[Idempotency] - Lost payments from rate limiting → solved by
[Throttle] - Race conditions creating duplicate records → solved by
[Concurrency]with key - Queue buildup from hanging requests → solved by
[Timeout]
Closes #7
Full Changelog: v1.3.7...v1.4.0
v1.3.7 - Add JCS canonicalization for signature verification
What's Changed
Bug Fix: Signature verification now properly matches Inngest's algorithm using JCS canonicalization.
The Problem
The Go SDK (and likely the server) applies JSON Canonicalization Scheme (RFC 8785) before computing signatures. Our SDK was signing raw bytes without canonicalization, causing signature mismatches for event-triggered functions (while cron jobs with simpler payloads worked).
The Fix
- Added
JsonCanonicalizerclass implementing RFC 8785 (JCS) - Signature verification now canonicalizes the JSON body before HMAC computation
- Falls back to raw bytes verification for compatibility
JCS Canonicalization ensures:
- Object keys are sorted lexicographically
- No extraneous whitespace
- Consistent number formatting
- Deterministic output regardless of input formatting
Files Changed
Inngest/Internal/JsonCanonicalizer.cs(new)Inngest/InngestClient.cs(updated signature verification)- Added 10 new tests for JCS canonicalizer
Full Changelog: v1.3.6...v1.3.7
v1.3.6 - Fix gzip-compressed POST signature verification
What's Changed
Bug Fix: Signature verification now works correctly for gzip-compressed POST requests.
The Problem
Inngest computes signatures on raw wire bytes (potentially gzip-compressed). When ASP.NET Core or a proxy decompresses the body before our code reads it, signature verification fails because we compute the HMAC on the decompressed bytes instead of the original compressed bytes.
The Fix
- Added
RawBodyMiddlewarethat captures raw request bytes early in the pipeline before any decompression - Updated
VerifySignature()to use raw bytes when available for HMAC verification - Falls back to string-based verification for uncompressed requests
No User Changes Required
The middleware is automatically added when calling UseInngest(), so existing code works without modification.
Files Changed
Inngest/Internal/RawBodyMiddleware.cs(new)Inngest/InngestMiddlewareExtensions.csInngest/InngestClient.cs
Tests
- Added 14 new tests for gzip signature verification
- All 114 tests passing
Full Changelog: v1.3.5...v1.3.6
v1.3.5
v1.3.4
Bug Fix
StepError now returns 500 to trigger Inngest retry mechanism
Previously, when a step threw an exception, the SDK wrapped it in StepInterruptException with StepOpCode.StepError and returned HTTP 206. Inngest interpreted 206 as "steps need scheduling" but couldn't handle StepError as a schedulable operation.
Changes
InngestClientnow checks forStepErrorinStepInterruptExceptionand returns HTTP 500 with proper error format to trigger retriesStepTools.Runnow re-throwsNonRetriableExceptionandRetryAfterExceptioninstead of wrapping them, so they're handled by dedicated catch blocks- Added comprehensive tests for step error scenarios
- Added example functions demonstrating error handling patterns
Behavior Summary
| Exception Type | HTTP Status | X-Inngest-No-Retry | Retry-After |
|---|---|---|---|
| Regular step error | 500 | false | - |
NonRetriableException |
400 | true | - |
RetryAfterException |
500 | false | {seconds} |
Full Changelog: v1.3.3...v1.3.4
v1.3.3
Bug Fixes
Major Fix: Step.SendEvent Now Works Correctly
This release fixes a critical issue where step.sendEvent would cause functions to restart indefinitely instead of properly progressing through steps.
Root Cause: The previous implementation used StepPlanned op code and didn't actually send events - it just told Inngest to handle it. But sendEvent is a "sync" step (like step.run) that should execute immediately and return results.
The Fix:
SendEventnow actually calls the Inngest Event API to send events- Returns
StepRunop code with the result{ ids: [...] }(likestep.rundoes) - Added
SendEventsDelegatetoStepToolsfor event sending capability - Added
SendEventsWithIdsAsynctoInngestClientto return event IDs from the API
Changes
Inngest/Steps/StepTools.cs- Complete rewrite ofSendEventto match TypeScript SDK behaviorInngest/Steps/StepOperation.cs- AddedNameproperty for step type identificationInngest/InngestClient.cs- AddedSendEventsWithIdsAsyncmethod, updatedStepToolscreation- Updated tests for new behavior
Expected Behavior After Fix
- First call: Function executes steps, hits
sendEvent, SDK calls Event API, returns 206 withStepRunresult - Inngest memoizes the
{ ids: [...] }result - Second call: Function receives memoized steps,
sendEventreturns cached IDs, function continues to completion
🤖 Generated with Claude Code