EdgeInteract
Interaction Audit Log
EdgeInteract maintains an immutable audit trail of every interaction request and response via IInteractionAuditStore. This provides a compliance-grade record showing who was asked, what they were asked, what they decided, and when.
What the Audit Log Contains
Each audit entry captures a complete snapshot of both the request and the response:
int_01HXY4Z8KQ2W3V9G
approval
interactionIdint_01HXY4Z8KQ2W3V9G
typeapproval
targetUserIdusr_mgr_jane
titleApprove Invoice INV-2026-0042
publishedAt2026-05-25T09:14:02Z
displayedAt2026-05-25T09:14:03Z
respondedAt2026-05-25T09:37:51Z
respondedByusr_mgr_jane
outcomeapproved
responseTimeMs1428000 (23m 48s)
statusresponded
correlationIdworkflow_inv_approval_run_7892
Audit Entry Schema
| Field | Type | Description |
|---|---|---|
interactionId | string | Unique ID of the interaction |
type | string | Interaction type (approval, confirmation, form, picker, notification) |
targetUserId | string | User or role the interaction was sent to |
title | string | Display title shown to the user |
requestPayload | JSON | Full payload sent with the request (may be omitted per retention policy) |
publishedAt | DateTime | When the request was published to the pipeline |
deliveredAt | DateTime? | When EdgeStream confirmed delivery to the client |
displayedAt | DateTime? | When the UI rendered the interaction for the user |
respondedAt | DateTime? | When the user's response was received |
respondedBy | string? | User ID that responded (may differ from target for role-targeted interactions) |
outcome | string? | Response outcome (approved, rejected, confirmed, submitted, etc.) |
responseData | JSON? | Additional data submitted with the response (form values, selected IDs, etc.) |
status | enum | Final status: responded, timed_out, blocked, cancelled |
correlationId | string? | Correlation ID from the original request (for workflow tracing) |
responseTimeMs | long? | Elapsed ms from displayedAt to respondedAt |
Registering the Audit Hook
The audit hook must be registered in the DI container to capture entries. It is not enabled by default:
// Program.cs
builder.Services.AddInteractionHook<AuditInteractionHook>();
// Configure audit store backend (SQL by default)
builder.Services.AddInteractionAuditStore<SqlInteractionAuditStore>(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("AuditDb");
options.RetainPayloadsForDays = 90; // payload retention (null = forever)
options.RetainEntriesForDays = 365; // entry retention (null = forever)
options.StoreResponseData = true; // include form/picker data in responseData
});
Querying the Audit Log
// Inject IInteractionAuditStore in any service or controller
public class AuditController(IInteractionAuditStore auditStore) : ControllerBase
{
[HttpGet("audit")]
[Authorize(Roles = "admin,compliance")]
public async Task<IActionResult> QueryAsync(
[FromQuery] string? userId,
[FromQuery] string? type,
[FromQuery] string? status,
[FromQuery] DateTime? from,
[FromQuery] DateTime? to,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
var query = new AuditQuery
{
TargetUserId = userId,
Type = type,
Statuses = status != null ? [Enum.Parse<InteractionStatus>(status)] : null,
CreatedAfter = from,
CreatedBefore = to,
Page = page,
PageSize = Math.Min(pageSize, 200)
};
var result = await auditStore.QueryAsync(query);
return Ok(result);
}
[HttpGet("audit/{interactionId}")]
[Authorize(Roles = "admin,compliance")]
public async Task<IActionResult> GetEntryAsync(string interactionId)
{
var entry = await auditStore.GetByIdAsync(interactionId);
if (entry is null) return NotFound();
return Ok(entry);
}
}
Compliance Use Cases
| Use Case | Query Pattern |
|---|---|
| Who approved invoice X? | Query by correlationId (workflow run ID) + type=approval |
| All decisions made by user U in last 30 days | Query by respondedBy=U, createdAfter=now-30d |
| All timed-out interactions last week | Query by status=timed_out, createdAfter=now-7d |
| All interactions sent to a specific role | Query by targetUserId (role identifier) |
| GDPR: all interactions involving user U | Query by targetUserId=U OR respondedBy=U |
Payload Retention
Request payloads and response data may contain sensitive business information. Use
RetainPayloadsForDays and a GDPR-compliant deletion policy. The audit entry itself (metadata, timestamps, outcomes) is retained separately from the payload blobs.
Custom Audit Store
Implement IInteractionAuditStore to store audit entries in any backend (Cosmos DB, Elasticsearch, S3, etc.):
public class ElasticInteractionAuditStore : IInteractionAuditStore
{
public Task WriteAsync(InteractionAuditEntry entry, CancellationToken ct = default)
=> _elasticClient.IndexDocumentAsync(entry, ct);
public Task<PagedResult<InteractionAuditEntry>> QueryAsync(AuditQuery query, CancellationToken ct = default)
=> _elasticClient.SearchAsync<InteractionAuditEntry>(BuildQuery(query), ct);
public Task<InteractionAuditEntry?> GetByIdAsync(string interactionId, CancellationToken ct = default)
=> _elasticClient.GetAsync<InteractionAuditEntry>(interactionId, ct);
}
// Register:
builder.Services.AddInteractionAuditStore<ElasticInteractionAuditStore>();