Skip to content

Commit 7d19c60

Browse files
Add 'Get Callbacks History by ID'
2 parents 3c01582 + 70c28ac commit 7d19c60

6 files changed

Lines changed: 260 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org).
1414
- Get Event Subscription by ID: Retrieve detailed information about specific event subscriptions
1515
- Delete Event Subscription: Remove event subscriptions programmatically
1616
- Get Callbacks History: Comprehensive webhook callback event history with advanced filtering and sorting capabilities
17+
- Get Callbacks History by ID: webhook callback event history related to subscription ID with advanced filtering and sorting capabilities
1718
- Document Template Operations:
1819
- Template Routing Management: Create, retrieve, and update routing configurations for document templates
1920
- Bulk Invite from Template: Send signing invitations to multiple recipients using templates
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
using SignNow.Net.Model.Requests;
6+
using SignNow.Net.Model.Requests.GetFolderQuery;
7+
8+
namespace SignNow.Net.Examples
9+
{
10+
[TestClass]
11+
public class GetCallbacksBySubscriptionIdExample : ExamplesBase
12+
{
13+
/// <summary>
14+
/// Demonstrates how to get webhook callback events for a specific event subscription.
15+
/// This example shows how to retrieve callback history filtered by subscription ID and analyze webhook delivery results.
16+
/// </summary>
17+
/// <see cref="https://docs.signnow.com/docs/signnow/callbacks-info/operations/list-v-2-event-subscription-callbacks-1"/>
18+
[TestMethod]
19+
public async Task GetCallbacksBySubscriptionIdAsync()
20+
{
21+
var subscriptions = await testContext.Events
22+
.GetEventSubscriptionsListAsync()
23+
.ConfigureAwait(false);
24+
25+
if (subscriptions?.Data == null || subscriptions.Data.Count == 0)
26+
{
27+
Console.WriteLine("No event subscriptions found. Please create an event subscription first.");
28+
return;
29+
}
30+
31+
var subscriptionId = subscriptions.Data.First().Id;
32+
Console.WriteLine($"Using subscription ID: {subscriptionId}");
33+
34+
// Example 1: Get all callbacks for a specific subscription with default options
35+
Console.WriteLine("\n=== Example 1: Get all callbacks for specific subscription ===");
36+
var allCallbacks = await testContext.Events
37+
.GetCallbacksBySubscriptionIdAsync(subscriptionId)
38+
.ConfigureAwait(false);
39+
40+
Console.WriteLine($"Total callbacks found for subscription {subscriptionId}: {allCallbacks.Data.Count}");
41+
Console.WriteLine($"Current page: {allCallbacks.Meta.Pagination.CurrentPage}");
42+
Console.WriteLine($"Per page: {allCallbacks.Meta.Pagination.PerPage}");
43+
Console.WriteLine($"Total pages: {allCallbacks.Meta.Pagination.TotalPages}");
44+
Console.WriteLine($"Total items: {allCallbacks.Meta.Pagination.Total}");
45+
46+
// Example 2: Filter successful callbacks for the subscription
47+
Console.WriteLine($"\n=== Example 2: Get successful callbacks for subscription {subscriptionId} ===");
48+
var successfulCallbacks = await testContext.Events
49+
.GetCallbacksBySubscriptionIdAsync(subscriptionId, new GetCallbacksOptions
50+
{
51+
Filters = f => f.Or(
52+
f => f.Date.Between(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow),
53+
f => f.Code.Between(200, 299),
54+
f => f.CallbackUrl.Like("example")
55+
),
56+
Sortings = s => s.Code(SortOrder.Descending),
57+
PerPage = 10
58+
})
59+
.ConfigureAwait(false);
60+
61+
Console.WriteLine($"Successful callbacks: {successfulCallbacks.Data.Count}");
62+
foreach (var callback in successfulCallbacks.Data.Take(3))
63+
{
64+
Console.WriteLine($" ID: {callback.Id}");
65+
Console.WriteLine($" Subscription ID: {callback.EventSubscriptionId}");
66+
Console.WriteLine($" Status Code: {callback.ResponseStatusCode}");
67+
Console.WriteLine($" Event: {callback.EventName}");
68+
Console.WriteLine($" Entity ID: {callback.EntityId}");
69+
Console.WriteLine($" Duration: {callback.Duration:F3}s");
70+
Console.WriteLine($" Start Time: {callback.RequestStartTime}");
71+
Console.WriteLine($" Callback Url: {callback.CallbackUrl}");
72+
Console.WriteLine();
73+
}
74+
}
75+
}
76+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using SignNow.Net.Model.Requests;
4+
using SignNow.Net.Model.Requests.GetFolderQuery;
5+
using UnitTests;
6+
7+
namespace AcceptanceTests
8+
{
9+
[TestClass]
10+
public class GetCallbacksBySubscriptionIdServiceTest : AuthorizedApiTestBase
11+
{
12+
[TestMethod]
13+
public async Task GetCallbacksBySubscriptionIdAsync_WithFilters_ReturnsFilteredResults()
14+
{
15+
var eventSubscriptions = await SignNowTestContext.Events
16+
.GetEventSubscriptionsListAsync()
17+
.ConfigureAwait(false);
18+
19+
if (eventSubscriptions?.Data == null || eventSubscriptions.Data.Count == 0)
20+
{
21+
Assert.Inconclusive("No event subscriptions available for testing");
22+
return;
23+
}
24+
25+
var subscriptionId = eventSubscriptions.Data[0].Id;
26+
27+
var options = new GetCallbacksOptions
28+
{
29+
Filters = f => f.Code.Between(200, 599),
30+
Sortings = s => s.StartTime(SortOrder.Descending),
31+
PerPage = 15
32+
};
33+
34+
var response = await SignNowTestContext.Events
35+
.GetCallbacksBySubscriptionIdAsync(subscriptionId, options)
36+
.ConfigureAwait(false);
37+
38+
Assert.IsNotNull(response);
39+
Assert.IsNotNull(response.Data);
40+
Assert.IsNotNull(response.Meta);
41+
Assert.AreEqual(15, response.Meta.Pagination.PerPage);
42+
43+
// Verify all callbacks belong to the specified subscription and match filter criteria
44+
foreach (var callback in response.Data)
45+
{
46+
Assert.AreEqual(subscriptionId, callback.EventSubscriptionId);
47+
Assert.IsTrue(callback.ResponseStatusCode >= 200 && callback.ResponseStatusCode <= 299,
48+
$"Callback status code {callback.ResponseStatusCode} should be in range 200-299");
49+
}
50+
}
51+
}
52+
}

SignNow.Net.Test/UnitTests/Services/EventSubscriptionServiceTest.cs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,4 +386,102 @@ public async Task GetCallbacksAsync_WithNullRequestHeaders_ShouldHandleGracefull
386386
Assert.IsNotNull(callback.RequestContent);
387387
Assert.IsNotNull(callback.RequestContent.Content);
388388
}
389-
}}
389+
390+
[TestMethod]
391+
public async Task GetCallbacksBySubscriptionIdAsync_WithValidSubscriptionId_ShouldReturnCallbacks()
392+
{
393+
var subscriptionId = "8a49e32e267e42a18e4a3967669f347e06e3b71e";
394+
var mockResponse = TestUtils.SerializeToJsonFormatted(new
395+
{
396+
data = new[]
397+
{
398+
new
399+
{
400+
id = "callback_123",
401+
application_name = "TestApp",
402+
entity_id = "doc_456",
403+
event_subscription_id = subscriptionId,
404+
event_subscription_active = true,
405+
entity_type = "document",
406+
event_name = "document.complete",
407+
callback_url = "https://example.com/webhook",
408+
request_method = "POST",
409+
duration = 1.5,
410+
request_start_time = 1609459200,
411+
request_end_time = 1609459205,
412+
request_headers = new
413+
{
414+
string_head = "test_value",
415+
int_head = 42,
416+
bool_head = true,
417+
float_head = 3.14f
418+
},
419+
response_content = "OK",
420+
response_status_code = 200,
421+
event_subscription_owner_email = "owner@example.com",
422+
request_content = new
423+
{
424+
meta = new
425+
{
426+
timestamp = 1609459200,
427+
@event = "document.complete",
428+
environment = "https://api.signnow.com/",
429+
initiator_id = "user_789",
430+
callback_url = "https://example.com/webhook",
431+
access_token = "***masked***"
432+
},
433+
content = new
434+
{
435+
document_id = "doc_456",
436+
document_name = "Test Document.pdf",
437+
user_id = "user_789",
438+
initiator_id = "user_789",
439+
initiator_email = "initiator@example.com"
440+
}
441+
}
442+
}
443+
},
444+
meta = new
445+
{
446+
pagination = new
447+
{
448+
total = 1,
449+
count = 1,
450+
per_page = 50,
451+
current_page = 1,
452+
total_pages = 1
453+
}
454+
}
455+
});
456+
457+
var service = new EventSubscriptionService(ApiBaseUrl, new Token(), SignNowClientMock(mockResponse));
458+
var options = new GetCallbacksOptions
459+
{
460+
Page = 1,
461+
PerPage = 50
462+
};
463+
464+
var response = await service.GetCallbacksBySubscriptionIdAsync(subscriptionId, options).ConfigureAwait(false);
465+
466+
Assert.IsNotNull(response);
467+
Assert.IsNotNull(response.Data);
468+
Assert.IsNotNull(response.Meta);
469+
Assert.AreEqual(1, response.Data.Count);
470+
471+
var callback = response.Data[0];
472+
Assert.AreEqual("callback_123", callback.Id);
473+
Assert.AreEqual("TestApp", callback.ApplicationName);
474+
Assert.AreEqual("doc_456", callback.EntityId);
475+
Assert.AreEqual(subscriptionId, callback.EventSubscriptionId);
476+
Assert.IsTrue(callback.EventSubscriptionActive);
477+
Assert.AreEqual(EventSubscriptionEntityType.Document, callback.EntityType);
478+
Assert.AreEqual(EventType.DocumentComplete, callback.EventName);
479+
Assert.AreEqual("https://example.com/webhook", callback.CallbackUrl.ToString());
480+
Assert.AreEqual(200, callback.ResponseStatusCode);
481+
482+
Assert.AreEqual(1, response.Meta.Pagination.Total);
483+
Assert.AreEqual(1, response.Meta.Pagination.Count);
484+
Assert.AreEqual(50, response.Meta.Pagination.PerPage);
485+
}
486+
}
487+
}

SignNow.Net/Interfaces/IEventSubscriptionService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,16 @@ public interface IEventSubscriptionService
9797
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
9898
/// <returns>List of callback events with metadata</returns>
9999
Task<CallbacksResponse> GetCallbacksAsync(GetCallbacksOptions options = default, CancellationToken cancellationToken = default);
100+
101+
/// <summary>
102+
/// Allows users to get the list of webhook events (events history) by the subscription ID.
103+
/// The results can be filtered and sorted. If the sort parameter is not indicated,
104+
/// the results are sorted by the start_time in descending order.
105+
/// </summary>
106+
/// <param name="subscriptionId">ID of the subscription</param>
107+
/// <param name="options">Options for filtering and sorting callbacks</param>
108+
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
109+
/// <returns>List of callback events for the specified subscription with metadata</returns>
110+
Task<CallbacksResponse> GetCallbacksBySubscriptionIdAsync(string subscriptionId, GetCallbacksOptions options = default, CancellationToken cancellationToken = default);
100111
}
101112
}

SignNow.Net/Service/EventSubscriptionService.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,5 +197,26 @@ public async Task<CallbacksResponse> GetCallbacksAsync(GetCallbacksOptions optio
197197
.RequestAsync<CallbacksResponse>(requestOptions, cancellationToken)
198198
.ConfigureAwait(false);
199199
}
200+
201+
/// <inheritdoc />
202+
public async Task<CallbacksResponse> GetCallbacksBySubscriptionIdAsync(string subscriptionId, GetCallbacksOptions options = default, CancellationToken cancellationToken = default)
203+
{
204+
Token.TokenType = TokenType.Bearer;
205+
206+
var query = options?.ToQueryString();
207+
var filters = string.IsNullOrEmpty(query)
208+
? string.Empty
209+
: $"?{query}";
210+
211+
var requestOptions = new GetHttpRequestOptions
212+
{
213+
RequestUrl = new Uri(ApiBaseUrl, $"/v2/event-subscriptions/{subscriptionId.ValidateId()}/callbacks{filters}"),
214+
Token = Token
215+
};
216+
217+
return await SignNowClient
218+
.RequestAsync<CallbacksResponse>(requestOptions, cancellationToken)
219+
.ConfigureAwait(false);
220+
}
200221
}
201222
}

0 commit comments

Comments
 (0)