diff --git a/BTCPayServer.Client/Models/WebhookInvoiceEvent.cs b/BTCPayServer.Client/Models/WebhookInvoiceEvent.cs index d5bef6451..5822b3aa4 100644 --- a/BTCPayServer.Client/Models/WebhookInvoiceEvent.cs +++ b/BTCPayServer.Client/Models/WebhookInvoiceEvent.cs @@ -7,11 +7,11 @@ namespace BTCPayServer.Client.Models { public class WebhookPayoutEvent : StoreWebhookEvent { - public WebhookPayoutEvent(string evtType, string storeId) + public WebhookPayoutEvent(string type, string storeId) { - if (!evtType.StartsWith("payout", StringComparison.InvariantCultureIgnoreCase)) - throw new ArgumentException("Invalid event type", nameof(evtType)); - Type = evtType; + if (!type.StartsWith("payout", StringComparison.InvariantCultureIgnoreCase)) + throw new ArgumentException("Invalid event type", nameof(type)); + Type = type; StoreId = storeId; } @@ -21,11 +21,11 @@ namespace BTCPayServer.Client.Models } public class WebhookPaymentRequestEvent : StoreWebhookEvent { - public WebhookPaymentRequestEvent(string evtType, string storeId) + public WebhookPaymentRequestEvent(string type, string storeId) { - if (!evtType.StartsWith("paymentrequest", StringComparison.InvariantCultureIgnoreCase)) - throw new ArgumentException("Invalid event type", nameof(evtType)); - Type = evtType; + if (!type.StartsWith("paymentrequest", StringComparison.InvariantCultureIgnoreCase)) + throw new ArgumentException("Invalid event type", nameof(type)); + Type = type; StoreId = storeId; } diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index fb446c3de..a277904cf 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -2331,7 +2331,7 @@ namespace BTCPayServer.Tests if (marked == InvoiceStatus.Settled) { Assert.Equal(InvoiceStatus.Settled, result.Status); - user.AssertHasWebhookEvent(WebhookEventType.InvoiceSettled, + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceSettled, o => { Assert.Equal(inv.Id, o.InvoiceId); @@ -2341,7 +2341,7 @@ namespace BTCPayServer.Tests if (marked == InvoiceStatus.Invalid) { Assert.Equal(InvoiceStatus.Invalid, result.Status); - var evt = user.AssertHasWebhookEvent(WebhookEventType.InvoiceInvalid, + var evt = await user.AssertHasWebhookEvent(WebhookEventType.InvoiceInvalid, o => { Assert.Equal(inv.Id, o.InvoiceId); diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index b8b0ae187..c850a8cdc 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -452,9 +452,9 @@ namespace BTCPayServer.Tests { private Client.Models.StoreWebhookData _wh; private FakeServer _server; - private readonly List _webhookEvents; + private readonly List _webhookEvents; private CancellationTokenSource _cts; - public WebhookListener(Client.Models.StoreWebhookData wh, FakeServer server, List webhookEvents) + public WebhookListener(Client.Models.StoreWebhookData wh, FakeServer server, List webhookEvents) { _wh = wh; _server = server; @@ -472,7 +472,7 @@ namespace BTCPayServer.Tests var callback = Encoding.UTF8.GetString(bytes); lock (_webhookEvents) { - _webhookEvents.Add(JsonConvert.DeserializeObject(callback)); + _webhookEvents.Add(JsonConvert.DeserializeObject(callback)); } req.Response.StatusCode = 200; _server.Done(); @@ -485,8 +485,13 @@ namespace BTCPayServer.Tests } } - public List WebhookEvents { get; set; } = new List(); - public TEvent AssertHasWebhookEvent(string eventType, Action assert) where TEvent : class + public class DummyStoreWebhookEvent : StoreWebhookEvent + { + + } + + public List WebhookEvents { get; set; } = new List(); + public async Task AssertHasWebhookEvent(string eventType, Action assert) where TEvent : class { int retry = 0; retry: @@ -510,7 +515,7 @@ retry: } if (retry < 3) { - Thread.Sleep(1000); + await Task.Delay(1000); retry++; goto retry; } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 5455b3686..b2f4462e6 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -73,6 +73,9 @@ using Xunit; using Xunit.Abstractions; using Xunit.Sdk; using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest; +using CreatePaymentRequestRequest = BTCPayServer.Client.Models.CreatePaymentRequestRequest; +using MarkPayoutRequest = BTCPayServer.Client.Models.MarkPayoutRequest; +using PaymentRequestData = BTCPayServer.Client.Models.PaymentRequestData; using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; namespace BTCPayServer.Tests @@ -1943,6 +1946,173 @@ namespace BTCPayServer.Tests entity.GetPaymentMethods().First().Calculate(); } + + + + [Fact()] + [Trait("Integration", "Integration")] + public async Task EnsureWebhooksTrigger() + { + using var tester = CreateServerTester(); + await tester.StartAsync(); + var user = tester.NewAccount(); + await user.GrantAccessAsync(); + user.RegisterDerivationScheme("BTC"); + await user.SetupWebhook(); + var client = await user.CreateClient(); + + + var invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() + { + Amount = 0.00m, + Currency = "BTC" + });; + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceCreated, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + + //invoice payment webhooks + invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() + { + Amount = 0.01m, + Currency = "BTC" + }); + + var invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model => + PaymentMethodId.Parse(model.PaymentMethod) == + new PaymentMethodId("BTC", BitcoinPaymentType.Instance)) + .PaymentLink, tester.ExplorerNode.Network); + var halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m)); + + invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model => + PaymentMethodId.Parse(model.PaymentMethod) == + new PaymentMethodId("BTC", BitcoinPaymentType.Instance)) + .PaymentLink, tester.ExplorerNode.Network); + var remainingPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC))); + + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceCreated, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceProcessing, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceReceivedPayment, + (WebhookInvoiceReceivedPaymentEvent x) => + { + Assert.Equal(invoice.Id, x.InvoiceId); + Assert.Contains(halfPaymentTx.ToString(), x.Payment.Id); + }); + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceReceivedPayment, + (WebhookInvoiceReceivedPaymentEvent x) => + { + Assert.Equal(invoice.Id, x.InvoiceId); + Assert.Contains(remainingPaymentTx.ToString(), x.Payment.Id); + }); + + await tester.ExplorerNode.GenerateAsync(1); + + await user.AssertHasWebhookEvent(WebhookEventType.InvoicePaymentSettled, + (WebhookInvoiceReceivedPaymentEvent x) => + { + Assert.Equal(invoice.Id, x.InvoiceId); + Assert.Contains(halfPaymentTx.ToString(), x.Payment.Id); + }); + await user.AssertHasWebhookEvent(WebhookEventType.InvoicePaymentSettled, + (WebhookInvoiceReceivedPaymentEvent x) => + { + Assert.Equal(invoice.Id, x.InvoiceId); + Assert.Contains(remainingPaymentTx.ToString(), x.Payment.Id); + }); + + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceSettled, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + + invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() + { + Amount = 0.01m, + Currency = "BTC", + }); + invoicePaymentRequest = new BitcoinUrlBuilder((await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id)).Single(model => + PaymentMethodId.Parse(model.PaymentMethod) == + new PaymentMethodId("BTC", BitcoinPaymentType.Instance)) + .PaymentLink, tester.ExplorerNode.Network); + halfPaymentTx = await tester.ExplorerNode.SendToAddressAsync(invoicePaymentRequest.Address, Money.Coins(invoicePaymentRequest.Amount.ToDecimal(MoneyUnit.BTC)/2m)); + + + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceCreated, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceReceivedPayment, + (WebhookInvoiceReceivedPaymentEvent x) => + { + Assert.Equal(invoice.Id, x.InvoiceId); + Assert.Contains(halfPaymentTx.ToString(), x.Payment.Id); + }); + + invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() + { + Amount = 0.01m, + Currency = "BTC" + }); + + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceCreated, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest() { Status = InvoiceStatus.Invalid}); + await user.AssertHasWebhookEvent(WebhookEventType.InvoiceInvalid, (WebhookInvoiceEvent x)=> Assert.Equal(invoice.Id, x.InvoiceId)); + + //payment request webhook test + var pr = await client.CreatePaymentRequest(user.StoreId, new CreatePaymentRequestRequest() + { + Amount = 100m, + Currency = "USD", + Title = "test pr", + //TODO: this is a bug, we should not have these props in create request + StoreId = user.StoreId, + FormResponse = new JObject(), + //END todo + Description = "lala baba" + }); + await user.AssertHasWebhookEvent(WebhookEventType.PaymentRequestCreated, (WebhookPaymentRequestEvent x)=> Assert.Equal(pr.Id, x.PaymentRequestId)); + pr = await client.UpdatePaymentRequest(user.StoreId, pr.Id, + new UpdatePaymentRequestRequest() { Title = "test pr updated", Amount = 100m, + Currency = "USD", + //TODO: this is a bug, we should not have these props in create request + StoreId = user.StoreId, + FormResponse = new JObject(), + //END todo + Description = "lala baba"}); + await user.AssertHasWebhookEvent(WebhookEventType.PaymentRequestUpdated, (WebhookPaymentRequestEvent x)=> Assert.Equal(pr.Id, x.PaymentRequestId)); + var inv = await client.PayPaymentRequest(user.StoreId, pr.Id, new PayPaymentRequestRequest() {}); + + await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest() { Status = InvoiceStatus.Settled}); + await user.AssertHasWebhookEvent(WebhookEventType.PaymentRequestStatusChanged, (WebhookPaymentRequestEvent x)=> + { + Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, x.Status); + Assert.Equal(pr.Id, x.PaymentRequestId); + }); + await client.ArchivePaymentRequest(user.StoreId, pr.Id); + await user.AssertHasWebhookEvent(WebhookEventType.PaymentRequestArchived, (WebhookPaymentRequestEvent x)=> Assert.Equal(pr.Id, x.PaymentRequestId)); + //payoyt webhooks test + var payout = await client.CreatePayout(user.StoreId, + new CreatePayoutThroughStoreRequest() + { + Amount = 0.0001m, + Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(), + Approved = true, + PaymentMethod = "BTC" + }); + await user.AssertHasWebhookEvent(WebhookEventType.PayoutCreated, (WebhookPayoutEvent x)=> Assert.Equal(payout.Id, x.PayoutId)); + await client.MarkPayout(user.StoreId, payout.Id, new MarkPayoutRequest(){ State = PayoutState.AwaitingApproval}); + await user.AssertHasWebhookEvent(WebhookEventType.PayoutUpdated, (WebhookPayoutEvent x)=> + { + Assert.Equal(payout.Id, x.PayoutId); + Assert.Equal(PayoutState.AwaitingApproval, x.PayoutState); + }); + + await client.ApprovePayout(user.StoreId, payout.Id, new ApprovePayoutRequest(){}); + await user.AssertHasWebhookEvent(WebhookEventType.PayoutApproved, (WebhookPayoutEvent x)=> + { + Assert.Equal(payout.Id, x.PayoutId); + Assert.Equal(PayoutState.AwaitingPayment, x.PayoutState); + }); + await client.CancelPayout(user.StoreId, payout.Id ); + await user.AssertHasWebhookEvent(WebhookEventType.PayoutUpdated, (WebhookPayoutEvent x)=> + { + Assert.Equal(payout.Id, x.PayoutId); + Assert.Equal(PayoutState.Cancelled, x.PayoutState); + }); + } + [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] public async Task InvoiceFlowThroughDifferentStatesCorrectly() diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs index 61b69ee32..5fcd356fc 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs @@ -168,6 +168,8 @@ namespace BTCPayServer.Controllers.Greenfield Status = Client.Models.PaymentRequestData.PaymentRequestStatus.Pending, Created = DateTimeOffset.UtcNow }; + request.FormResponse = null; + request.StoreId = storeId; pr.SetBlob(request); pr = await _paymentRequestRepository.CreateOrUpdatePaymentRequest(pr); return Ok(FromModel(pr)); @@ -196,6 +198,9 @@ namespace BTCPayServer.Controllers.Greenfield } var updatedPr = pr.First(); + var blob = updatedPr.GetBlob(); + request.FormResponse = blob.FormResponse; + request.StoreId = storeId; updatedPr.SetBlob(request); return Ok(FromModel(await _paymentRequestRepository.CreateOrUpdatePaymentRequest(updatedPr))); diff --git a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs index fa4c005c4..8e96c7768 100644 --- a/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs +++ b/BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs @@ -225,7 +225,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike { if (payout.State != PayoutState.AwaitingPayment) { - _eventAggregator.Publish(new PayoutEvent(null, payout)); + _eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated, payout)); } } } diff --git a/BTCPayServer/HostedServices/PullPaymentHostedService.cs b/BTCPayServer/HostedServices/PullPaymentHostedService.cs index 6883ef632..a43d6134a 100644 --- a/BTCPayServer/HostedServices/PullPaymentHostedService.cs +++ b/BTCPayServer/HostedServices/PullPaymentHostedService.cs @@ -517,7 +517,7 @@ namespace BTCPayServer.HostedServices } payout.State = req.Request.State; await ctx.SaveChangesAsync(); - _eventAggregator.Publish(new PayoutEvent(null, payout)); + _eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated, payout)); req.Completion.SetResult(MarkPayoutRequest.PayoutPaidResult.Ok); } catch (Exception ex) @@ -739,7 +739,7 @@ namespace BTCPayServer.HostedServices foreach (var keyValuePair in result.Where(pair => pair.Value == MarkPayoutRequest.PayoutPaidResult.Ok)) { var payout = payouts.First(p => p.Id == keyValuePair.Key); - _eventAggregator.Publish(new PayoutEvent(null, payout)); + _eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated, payout)); } cancel.Completion.TrySetResult(result); } @@ -959,7 +959,7 @@ namespace BTCPayServer.HostedServices public JObject Metadata { get; set; } } - public record PayoutEvent(PayoutEvent.PayoutEventType? Type, PayoutData Payout) + public record PayoutEvent(PayoutEvent.PayoutEventType Type, PayoutData Payout) { public enum PayoutEventType { diff --git a/BTCPayServer/PayoutProcessors/BaseAutomatedPayoutProcessor.cs b/BTCPayServer/PayoutProcessors/BaseAutomatedPayoutProcessor.cs index 620271c92..630d45674 100644 --- a/BTCPayServer/PayoutProcessors/BaseAutomatedPayoutProcessor.cs +++ b/BTCPayServer/PayoutProcessors/BaseAutomatedPayoutProcessor.cs @@ -120,7 +120,7 @@ public abstract class BaseAutomatedPayoutProcessor : BaseAsyncService where T foreach (var payoutData in payouts.Where(payoutData => payoutData.State != PayoutState.AwaitingPayment)) { - _eventAggregator.Publish(new PayoutEvent(null, payoutData)); + _eventAggregator.Publish(new PayoutEvent(PayoutEvent.PayoutEventType.Updated, payoutData)); } } diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.webhooks.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.webhooks.json index 1fcd4183e..2883131b5 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.webhooks.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.webhooks.json @@ -693,10 +693,10 @@ "nullable": false }, "metadata": { - "type": "object", - "description": "User-supplied metadata added to the invoice at the time of its creation", - "nullable": false - } + "type": "object", + "description": "User-supplied metadata added to the invoice at the time of its creation", + "nullable": false + } } } ] @@ -1129,6 +1129,420 @@ } } } + }, + "PaymentRequestCreated": { + "post": { + "operationId": "Webhook_PaymentRequestCreated", + "summary": "PaymentRequestCreated", + "description": "A new payment request has been created", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paymentRequestId": { + "type": "string", + "description": "The id of the payment request", + "nullable": false + }, + "status": { + "type": "string", + "enum": [ + "Pending", + "Completed", + "Expired" + ], + "description": "The status of the payment request", + "nullable": false + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PaymentRequestUpdated": { + "post": { + "operationId": "Webhook_PaymentRequestUpdated", + "summary": "PaymentRequestUpdated", + "description": "A payment request has been updated", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paymentRequestId": { + "type": "string", + "description": "The id of the payment request", + "nullable": false + }, + "status": { + "type": "string", + "enum": [ + "Pending", + "Completed", + "Expired" + ], + "description": "The status of the payment request", + "nullable": false + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PaymentRequestArchived": { + "post": { + "operationId": "Webhook_PaymentRequestArchved", + "summary": "PaymentRequestArchived", + "description": "A payment request has been archived", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paymentRequestId": { + "type": "string", + "description": "The id of the payment request", + "nullable": false + }, + "status": { + "type": "string", + "enum": [ + "Pending", + "Completed", + "Expired" + ], + "description": "The status of the payment request", + "nullable": false + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PaymentRequestStatusChanged": { + "post": { + "operationId": "Webhook_PaymentRequestStatusChanged", + "summary": "PaymentRequestStatusChanged", + "description": "A payment request has had its status changed", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "paymentRequestId": { + "type": "string", + "description": "The id of the payment request", + "nullable": false + }, + "status": { + "type": "string", + "enum": [ + "Pending", + "Completed", + "Expired" + ], + "description": "The status of the payment request", + "nullable": false + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PayoutCreated": { + "post": { + "operationId": "Webhook_PayoutCreated", + "summary": "PayoutCreated", + "description": "A payout has been created", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payoutId": { + "type": "string", + "description": "The id of the payout", + "nullable": false + }, + "pullPaymentId": { + "type": "string", + "description": "The id of the pull payment this payout belongs to", + "nullable": true + }, + "payoutState": { + "$ref": "#/components/schemas/PayoutState" + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PayoutApproved": { + "post": { + "operationId": "Webhook_PayoutApproved", + "summary": "PayoutApproved", + "description": "A payout has been approved", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payoutId": { + "type": "string", + "description": "The id of the payout", + "nullable": false + }, + "pullPaymentId": { + "type": "string", + "description": "The id of the pull payment this payout belongs to", + "nullable": true + }, + "payoutState": { + "$ref": "#/components/schemas/PayoutState" + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "PayoutUpdated": { + "post": { + "operationId": "Webhook_PayoutUpdated", + "summary": "PayoutUpdated", + "description": "A payout has been updated", + "parameters": [ + { + "in": "header", + "name": "BTCPay-Sig", + "required": true, + "description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`", + "schema": { + "type": "string", + "example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9" + } + } + ], + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payoutId": { + "type": "string", + "description": "The id of the payout", + "nullable": false + }, + "pullPaymentId": { + "type": "string", + "description": "The id of the pull payment this payout belongs to", + "nullable": true + }, + "payoutState": { + "$ref": "#/components/schemas/PayoutState" + } + } + } + } + } + }, + "responses": { + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } } }, "tags": [