From b150d8c8ecdd57e9558984dd534a34e40d26cd54 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 12:21:15 +0200 Subject: [PATCH 1/9] change topic type to string --- controllers/invoicestream.ctrl.go | 9 +++++---- lib/service/invoices.go | 3 ++- lib/service/invoicesubscription.go | 3 ++- lib/service/pubsub.go | 10 +++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 2f1e0bda..5c102b03 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -2,6 +2,7 @@ package controllers import ( "net/http" + "strconv" "time" "github.com/getAlby/lndhub.go/common" @@ -40,13 +41,13 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } //start subscription - subId := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) + subId := controller.svc.InvoicePubSub.Subscribe(strconv.FormatInt(userId, 10), invoiceChan) //start with keepalive message err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) if err != nil { controller.svc.Logger.Error(err) - controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + controller.svc.InvoicePubSub.Unsubscribe(subId, strconv.FormatInt(userId, 10)) return err } fromPaymentHash := c.QueryParam("since_payment_hash") @@ -54,7 +55,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error err = controller.writeMissingInvoices(c, userId, ws, fromPaymentHash) if err != nil { controller.svc.Logger.Error(err) - controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + controller.svc.InvoicePubSub.Unsubscribe(subId, strconv.FormatInt(userId, 10)) return err } } @@ -89,7 +90,7 @@ SocketLoop: } } } - controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + controller.svc.InvoicePubSub.Unsubscribe(subId, strconv.FormatInt(userId, 10)) return nil } diff --git a/lib/service/invoices.go b/lib/service/invoices.go index c95ab8da..0b6fbe74 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "math/rand" + "strconv" "time" "github.com/getAlby/lndhub.go/common" @@ -97,7 +98,7 @@ func (svc *LndhubService) SendInternalPayment(ctx context.Context, invoice *mode // could not save the invoice of the recipient return sendPaymentResponse, err } - svc.InvoicePubSub.Publish(incomingInvoice.UserID, incomingInvoice) + svc.InvoicePubSub.Publish(strconv.FormatInt(incomingInvoice.UserID, 10), incomingInvoice) return sendPaymentResponse, nil } diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index 580f9004..e4be385b 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/hex" "fmt" + "strconv" "strings" "time" @@ -98,7 +99,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * svc.Logger.Errorf("Failed to commit DB transaction user_id:%v invoice_id:%v %v", invoice.UserID, invoice.ID, err) return err } - svc.InvoicePubSub.Publish(invoice.UserID, invoice) + svc.InvoicePubSub.Publish(strconv.FormatInt(invoice.UserID, 10), invoice) return nil } diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index 47b04866..59f17dd9 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -8,16 +8,16 @@ import ( type Pubsub struct { mu sync.RWMutex - subs map[int64]map[string]chan models.Invoice + subs map[string]map[string]chan models.Invoice } func NewPubsub() *Pubsub { ps := &Pubsub{} - ps.subs = make(map[int64]map[string]chan models.Invoice) + ps.subs = make(map[string]map[string]chan models.Invoice) return ps } -func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string) { +func (ps *Pubsub) Subscribe(topic string, ch chan models.Invoice) (subId string) { ps.mu.Lock() defer ps.mu.Unlock() if ps.subs[topic] == nil { @@ -29,7 +29,7 @@ func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string) return subId } -func (ps *Pubsub) Unsubscribe(id string, topic int64) { +func (ps *Pubsub) Unsubscribe(id string, topic string) { ps.mu.Lock() defer ps.mu.Unlock() if ps.subs[topic] == nil { @@ -42,7 +42,7 @@ func (ps *Pubsub) Unsubscribe(id string, topic int64) { delete(ps.subs[topic], id) } -func (ps *Pubsub) Publish(topic int64, msg models.Invoice) { +func (ps *Pubsub) Publish(topic string, msg models.Invoice) { ps.mu.RLock() defer ps.mu.RUnlock() From 017bf282fd02d4495caf45b4a8f939b610a7be65 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 12:25:28 +0200 Subject: [PATCH 2/9] publish incoming and outgoing payments to respective topics --- lib/service/invoices.go | 2 ++ lib/service/invoicesubscription.go | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 0b6fbe74..095aa58c 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -99,6 +99,7 @@ func (svc *LndhubService) SendInternalPayment(ctx context.Context, invoice *mode return sendPaymentResponse, err } svc.InvoicePubSub.Publish(strconv.FormatInt(incomingInvoice.UserID, 10), incomingInvoice) + svc.InvoicePubSub.Publish(common.InvoiceTypeIncoming, incomingInvoice) return sendPaymentResponse, nil } @@ -309,6 +310,7 @@ func (svc *LndhubService) HandleSuccessfulPayment(ctx context.Context, invoice * svc.Logger.Info(amountMsg) sentry.CaptureMessage(amountMsg) } + svc.InvoicePubSub.Publish(common.InvoiceTypeOutgoing, *invoice) return nil } diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index e4be385b..a751e738 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -100,6 +100,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * return err } svc.InvoicePubSub.Publish(strconv.FormatInt(invoice.UserID, 10), invoice) + svc.InvoicePubSub.Publish(common.InvoiceTypeIncoming, invoice) return nil } From 03baba8c37485f8e7eeed02d04fe4a4795afc9cc Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 13:54:15 +0200 Subject: [PATCH 3/9] add webhook service --- lib/service/config.go | 1 + main.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/service/config.go b/lib/service/config.go index 61e5f800..f7675221 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -17,4 +17,5 @@ type Config struct { BurstRateLimit int `envconfig:"BURST_RATE_LIMIT" default:"1"` EnablePrometheus bool `envconfig:"ENABLE_PROMETHEUS" default:"false"` PrometheusPort int `envconfig:"PROMETHEUS_PORT" default:"9092"` + WebhookUrl string `envconfig:"WEBHOOK_URL"` } diff --git a/main.go b/main.go index 25e072be..32194537 100644 --- a/main.go +++ b/main.go @@ -168,6 +168,13 @@ func main() { // Subscribe to LND invoice updates in the background go svc.InvoiceUpdateSubscription(context.Background()) + //Start webhook subscription + if svc.Config.WebhookUrl != "" { + webhookCtx, cancelWebhook := context.WithCancel(context.Background()) + go svc.StartWebhookSubscribtion(webhookCtx) + defer cancelWebhook() + } + //Start Prometheus server if necessary var echoPrometheus *echo.Echo if svc.Config.EnablePrometheus { From 252b1fe78453bea5d73e3d92f8691032e145d34e Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 13:54:23 +0200 Subject: [PATCH 4/9] add webhook service --- lib/service/webhook.go | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/service/webhook.go diff --git a/lib/service/webhook.go b/lib/service/webhook.go new file mode 100644 index 00000000..3311387c --- /dev/null +++ b/lib/service/webhook.go @@ -0,0 +1,53 @@ +package service + +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/getAlby/lndhub.go/common" + "github.com/getAlby/lndhub.go/db/models" +) + +func (svc *LndhubService) StartWebhookSubscribtion(ctx context.Context) { + + svc.Logger.Infof("Starting webhook subscription with webhook url %s", svc.Config.WebhookUrl) + incomingInvoices := make(chan models.Invoice) + outgoingInvoices := make(chan models.Invoice) + svc.InvoicePubSub.Subscribe(common.InvoiceTypeIncoming, incomingInvoices) + svc.InvoicePubSub.Subscribe(common.InvoiceTypeOutgoing, outgoingInvoices) + for { + select { + case <-ctx.Done(): + return + case incoming := <-incomingInvoices: + svc.postToWebhook(incoming) + case outgoing := <-outgoingInvoices: + svc.postToWebhook(outgoing) + } + } +} +func (svc *LndhubService) postToWebhook(invoice models.Invoice) { + + payload := new(bytes.Buffer) + err := json.NewEncoder(payload).Encode(invoice) + if err != nil { + svc.Logger.Error(err) + return + } + + resp, err := http.Post(svc.Config.WebhookUrl, "application/json", payload) + if err != nil { + svc.Logger.Error(err) + return + } + if resp.StatusCode != http.StatusOK { + msg, err := ioutil.ReadAll(resp.Body) + if err != nil { + svc.Logger.Error(err) + } + svc.Logger.Errorf("Webhook status code was %d, body: %s", resp.StatusCode, msg) + } +} From ea74fadeef459a7a5c5de7d18d5632af687cc269 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 14:35:29 +0200 Subject: [PATCH 5/9] add webhook test --- integration_tests/webhook_test.go | 109 ++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 integration_tests/webhook_test.go diff --git a/integration_tests/webhook_test.go b/integration_tests/webhook_test.go new file mode 100644 index 00000000..7ec561f3 --- /dev/null +++ b/integration_tests/webhook_test.go @@ -0,0 +1,109 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "log" + "net/http" + "net/http/httptest" + "testing" + + "github.com/getAlby/lndhub.go/common" + "github.com/getAlby/lndhub.go/controllers" + "github.com/getAlby/lndhub.go/db/models" + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lib/tokens" + "github.com/getAlby/lndhub.go/lnd" + "github.com/go-playground/validator/v10" + "github.com/labstack/echo/v4" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type WebHookTestSuite struct { + TestSuite + fundingClient *lnd.LNDWrapper + service *service.LndhubService + userLogin ExpectedCreateUserResponseBody + userToken string + webHookServer *httptest.Server + invoiceChan chan (models.Invoice) + invoiceUpdateSubCancelFn context.CancelFunc +} + +func (suite *WebHookTestSuite) SetupSuite() { + lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ + Address: lnd2RegtestAddress, + MacaroonHex: lnd2RegtestMacaroonHex, + }) + if err != nil { + log.Fatalf("Error setting up funding client: %v", err) + } + suite.fundingClient = lndClient + + suite.invoiceChan = make(chan models.Invoice) + webhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + invoice := models.Invoice{} + err = json.NewDecoder(r.Body).Decode(&invoice) + if err != nil { + suite.echo.Logger.Error(err) + close(suite.invoiceChan) + return + } + suite.invoiceChan <- invoice + })) + suite.webHookServer = webhookServer + svc, err := LndHubTestServiceInit(nil) + if err != nil { + log.Fatalf("Error initializing test service: %v", err) + } + svc.Config.WebhookUrl = suite.webHookServer.URL + + users, userTokens, err := createUsers(svc, 1) + if err != nil { + log.Fatalf("Error creating test users: %v", err) + } + // Subscribe to LND invoice updates in the background + // store cancel func to be called in tear down suite + ctx, cancel := context.WithCancel(context.Background()) + suite.invoiceUpdateSubCancelFn = cancel + go svc.InvoiceUpdateSubscription(ctx) + + go svc.StartWebhookSubscribtion(ctx) + + suite.service = svc + e := echo.New() + + e.HTTPErrorHandler = responses.HTTPErrorHandler + e.Validator = &lib.CustomValidator{Validator: validator.New()} + suite.echo = e + suite.userLogin = users[0] + suite.userToken = userTokens[0] + suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret))) + suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice) +} +func (suite *WebHookTestSuite) TestWebHook() { + // create incoming invoice and fund account + invoice := suite.createAddInvoiceReq(1000, "integration test webhook", suite.userToken) + sendPaymentRequest := lnrpc.SendRequest{ + PaymentRequest: invoice.PayReq, + FeeLimit: nil, + } + _, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + invoiceFromWebhook := <-suite.invoiceChan + assert.Equal(suite.T(), "integration test webhook", invoiceFromWebhook.Memo) + assert.Equal(suite.T(), common.InvoiceTypeIncoming, invoiceFromWebhook.Type) +} +func (suite *WebHookTestSuite) TearDownSuite() { + suite.invoiceUpdateSubCancelFn() + suite.webHookServer.Close() + clearTable(suite.service, "invoices") +} + +func TestWebHookSuite(t *testing.T) { + suite.Run(t, new(WebHookTestSuite)) +} From 2fac4fcf09aabd3bcaef4cf799e9652ce75f00b2 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 13:39:53 +0200 Subject: [PATCH 6/9] README: add example --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 8a14bff9..02134b4b 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ vim .env # edit your config + `BURST_RATE_LIMIT`: (default: 1) Rate limit burst + `ENABLE_PROMETHEUS`: (default: false) Enable Prometheus metrics to be exposed + `PROMETHEUS_PORT`: (default: 9092) Prometheus port (path: `/metrics`) ++ `WEBHOOK_URL`: Optional. Callback URL for incoming and outgoing payment events, see below. ## Developing ```shell @@ -77,6 +78,37 @@ LndHub.go supports PostgreSQL and SQLite as database backend. But SQLite does no Prometheus metrics can be optionally exposed through the `ENABLE_PROMETHEUS` environment variable. For an example dashboard, see https://grafana.com/grafana/dashboards/10913. +## Webhooks + +If `WEBHOOK_URL` is specified, a http POST request will be dispatched at that location when an incoming payment is settled, or an outgoing payment is completed. Example payload: + +``` +{ + "id": 690, + "type": "outgoing", + "user_id": 286, + "User": null, + "amount": 10, + "fee": 0, + "memo": "memo", + "description_hash": "", + "payment_request": "lnbcrt100n1p3xjtw3pp5s56uj7d4lpchz0d25jggq0au5r65skvtp8f5dvm5e3l4c6cxg9aqdqy09eqcqzpgxqrrsssp53pyhm6j2vl4sr7ul8pa4sfvptk96yn2lkeceh8z2etkl8emapkgq9qyyssqgpxy39ktu60gkct8y7ehkemu5c77dffg905cd6sr5cukgjna2nwnfew65zkems5sm5xmdllrkf8ym3dhc2asj7hn27tq7xe0dq5hq5spt2g4c2", + "destination_pubkey_hex": "025c1d5d1b4c983cc6350fc2d756fbb59b4dc365e45e87f8e3afe07e24013e8220", + "DestinationCustomRecords": null, + "r_hash": "8535c979b5f871713daaa490803fbca0f548598b09d346b374cc7f5c6b06417a", + "preimage": "fa40fd77183b1bae11fec8a1479f08e210f1711f07a5a6a45b8d46a34cd820b1", + "internal": false, + "keysend": false, + "state": "settled", + "error_message": "", + "add_index": 0, + "CreatedAt": "2022-04-27T13:50:43.938597+02:00", + "ExpiresAt": "2022-04-27T14:49:37+02:00", + "updated_at": "2022-04-27T13:50:44.313549+02:00", + "settled_at": "2022-04-27T13:50:44.313539+02:00" +} +``` + ### Ideas + Using low level database constraints to prevent data inconsistencies + Follow double-entry bookkeeping ideas (Every transaction is a debit of one account and a credit to another one) From f11098b7b4c74b4b837a1d6c7049eb6ca2070587 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 13:42:43 +0200 Subject: [PATCH 7/9] add webhook url as argument --- integration_tests/webhook_test.go | 2 +- lib/service/webhook.go | 8 ++++---- main.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration_tests/webhook_test.go b/integration_tests/webhook_test.go index 7ec561f3..b1c21df7 100644 --- a/integration_tests/webhook_test.go +++ b/integration_tests/webhook_test.go @@ -72,7 +72,7 @@ func (suite *WebHookTestSuite) SetupSuite() { suite.invoiceUpdateSubCancelFn = cancel go svc.InvoiceUpdateSubscription(ctx) - go svc.StartWebhookSubscribtion(ctx) + go svc.StartWebhookSubscribtion(ctx, svc.Config.WebhookUrl) suite.service = svc e := echo.New() diff --git a/lib/service/webhook.go b/lib/service/webhook.go index 3311387c..5cc02520 100644 --- a/lib/service/webhook.go +++ b/lib/service/webhook.go @@ -11,7 +11,7 @@ import ( "github.com/getAlby/lndhub.go/db/models" ) -func (svc *LndhubService) StartWebhookSubscribtion(ctx context.Context) { +func (svc *LndhubService) StartWebhookSubscribtion(ctx context.Context, url string) { svc.Logger.Infof("Starting webhook subscription with webhook url %s", svc.Config.WebhookUrl) incomingInvoices := make(chan models.Invoice) @@ -23,13 +23,13 @@ func (svc *LndhubService) StartWebhookSubscribtion(ctx context.Context) { case <-ctx.Done(): return case incoming := <-incomingInvoices: - svc.postToWebhook(incoming) + svc.postToWebhook(incoming, url) case outgoing := <-outgoingInvoices: - svc.postToWebhook(outgoing) + svc.postToWebhook(outgoing, url) } } } -func (svc *LndhubService) postToWebhook(invoice models.Invoice) { +func (svc *LndhubService) postToWebhook(invoice models.Invoice, url string) { payload := new(bytes.Buffer) err := json.NewEncoder(payload).Encode(invoice) diff --git a/main.go b/main.go index 32194537..0e6d7eab 100644 --- a/main.go +++ b/main.go @@ -171,7 +171,7 @@ func main() { //Start webhook subscription if svc.Config.WebhookUrl != "" { webhookCtx, cancelWebhook := context.WithCancel(context.Background()) - go svc.StartWebhookSubscribtion(webhookCtx) + go svc.StartWebhookSubscribtion(webhookCtx, svc.Config.WebhookUrl) defer cancelWebhook() } From ac472e4d79e69426585570ac31e85d4e58ed3c76 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 3 May 2022 09:18:47 +0200 Subject: [PATCH 8/9] update json schema invoice --- db/models/invoice.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/db/models/invoice.go b/db/models/invoice.go index d21a6e38..950e64ab 100644 --- a/db/models/invoice.go +++ b/db/models/invoice.go @@ -12,23 +12,23 @@ type Invoice struct { ID int64 `json:"id" bun:",pk,autoincrement"` Type string `json:"type" validate:"required"` UserID int64 `json:"user_id" validate:"required"` - User *User `bun:"rel:belongs-to,join:user_id=id"` + User *User `json:"-" bun:"rel:belongs-to,join:user_id=id"` Amount int64 `json:"amount" validate:"gte=0"` Fee int64 `json:"fee" bun:",nullzero"` Memo string `json:"memo" bun:",nullzero"` - DescriptionHash string `json:"description_hash" bun:",nullzero"` + DescriptionHash string `json:"description_hash,omitempty" bun:",nullzero"` PaymentRequest string `json:"payment_request" bun:",nullzero"` DestinationPubkeyHex string `json:"destination_pubkey_hex" bun:",notnull"` - DestinationCustomRecords map[uint64][]byte `bun:"-"` + DestinationCustomRecords map[uint64][]byte `json:"custom_records,omitempty" bun:"-"` RHash string `json:"r_hash"` Preimage string `json:"preimage" bun:",nullzero"` - Internal bool `json:"internal" bun:",nullzero"` + Internal bool `json:"-" bun:",nullzero"` Keysend bool `json:"keysend" bun:",nullzero"` State string `json:"state" bun:",default:'initialized'"` - ErrorMessage string `json:"error_message" bun:",nullzero"` - AddIndex uint64 `json:"add_index" bun:",nullzero"` - CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` - ExpiresAt bun.NullTime `bun:",nullzero"` + ErrorMessage string `json:"error_message,omitempty" bun:",nullzero"` + AddIndex uint64 `json:"-" bun:",nullzero"` + CreatedAt time.Time `json:"created_at" bun:",nullzero,notnull,default:current_timestamp"` + ExpiresAt bun.NullTime `json:"expires_at" bun:",nullzero"` UpdatedAt bun.NullTime `json:"updated_at"` SettledAt bun.NullTime `json:"settled_at"` } From 00bfc568e7847a2fec05783936b511e4d2c6dc92 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 3 May 2022 09:33:21 +0200 Subject: [PATCH 9/9] update README --- README.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 02134b4b..eda97cca 100644 --- a/README.md +++ b/README.md @@ -84,28 +84,26 @@ If `WEBHOOK_URL` is specified, a http POST request will be dispatched at that lo ``` { - "id": 690, - "type": "outgoing", - "user_id": 286, - "User": null, - "amount": 10, + "id": 721, + "type": "incoming", //incoming, outgoing + "user_id": 299, + "amount": 1000, "fee": 0, - "memo": "memo", + "memo": "fill wallet", "description_hash": "", - "payment_request": "lnbcrt100n1p3xjtw3pp5s56uj7d4lpchz0d25jggq0au5r65skvtp8f5dvm5e3l4c6cxg9aqdqy09eqcqzpgxqrrsssp53pyhm6j2vl4sr7ul8pa4sfvptk96yn2lkeceh8z2etkl8emapkgq9qyyssqgpxy39ktu60gkct8y7ehkemu5c77dffg905cd6sr5cukgjna2nwnfew65zkems5sm5xmdllrkf8ym3dhc2asj7hn27tq7xe0dq5hq5spt2g4c2", - "destination_pubkey_hex": "025c1d5d1b4c983cc6350fc2d756fbb59b4dc365e45e87f8e3afe07e24013e8220", - "DestinationCustomRecords": null, - "r_hash": "8535c979b5f871713daaa490803fbca0f548598b09d346b374cc7f5c6b06417a", - "preimage": "fa40fd77183b1bae11fec8a1479f08e210f1711f07a5a6a45b8d46a34cd820b1", - "internal": false, + "payment_request": "lnbcrt10u1p38p4ehpp5xp07pda02vk40wxd9gyrene8qzheucz7ast435u9jwxejs6f0v5sdqjve5kcmpqwaskcmr9wscqzpgxqyz5vqsp56nyve3v5fw306j74nmewv7t5ey3aer2khjrrwznh4k2vuw44unzq9qyyssqv2wq9hn7a39x8cvz9fvpzul87u4kc4edf0t6jukzvmx8v5swl3jqg8p3sh6czkepczcjkm523q9x8yswsastctnsns3q9d26szu703gpwh7a09", + "destination_pubkey_hex": "0376442c750766d5d127512609a5618d9aa82db2d06aae226084da92a3e133acda", + "custom_records": { + "5482373484": "YWY4MDhlZDUxZjNmY2YxNWMxYWI3MmM3ODVhNWI1MDE=" + }, //only set when keysend=true + "r_hash": "305fe0b7af532d57b8cd2a083ccf2700af9e605eec1758d385938d9943497b29", + "preimage": "3735363531303032626332356439376136643461326434336335626434653035", "keysend": false, "state": "settled", - "error_message": "", - "add_index": 0, - "CreatedAt": "2022-04-27T13:50:43.938597+02:00", - "ExpiresAt": "2022-04-27T14:49:37+02:00", - "updated_at": "2022-04-27T13:50:44.313549+02:00", - "settled_at": "2022-04-27T13:50:44.313539+02:00" + "created_at": "2022-05-03T09:18:15.15774+02:00", + "expires_at": "2022-05-04T09:18:15.157597+02:00", + "updated_at": "2022-05-03T09:18:19.837567+02:00", + "settled_at": "2022-05-03T09:18:19+02:00" } ```