From cb7b9074644e74cac5b59aa4b8e73f6d01f0362d Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:29:58 +0100 Subject: [PATCH 1/4] feat(webhooks): emit settlement outcome events --- src/services/settlement-reconciliation.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/services/settlement-reconciliation.ts b/src/services/settlement-reconciliation.ts index 3ade8f3..b726b55 100644 --- a/src/services/settlement-reconciliation.ts +++ b/src/services/settlement-reconciliation.ts @@ -11,6 +11,7 @@ import { verifyPaymentOperation, getTransactionPayments, } from "./horizonService"; +import { emitEvent } from "./event"; const log = pino({ name: "settlement-reconciliation" }); @@ -178,6 +179,10 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash, reason: err.message }, }); + emitEvent({ + eventType: "settlement.failed", + payload: { settlementId: settlement.id, reason: err.message }, + }); recLog.error( { id: settlement.id, hash, reason: err.message }, "settlement transaction verification failed" @@ -204,6 +209,10 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash }, }); + emitEvent({ + eventType: "settlement.completed", + payload: { settlementId: settlement.id, stellarTxHash: hash }, + }); recLog.info({ id: settlement.id, hash }, "settlement completed"); } else { await applySettlementTransition({ @@ -222,6 +231,10 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash, reason: "transaction_failed" }, }); + emitEvent({ + eventType: "settlement.failed", + payload: { settlementId: settlement.id, reason: "transaction_failed" }, + }); recLog.error({ id: settlement.id, hash }, "settlement transaction failed on Stellar"); } } From 559fca14fdcfcd12b5bc6abb61b6d8f824dd4ece Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:34:00 +0100 Subject: [PATCH 2/4] fix(webhooks): include settlement group in events --- src/services/settlement-reconciliation.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/settlement-reconciliation.ts b/src/services/settlement-reconciliation.ts index b726b55..3033af4 100644 --- a/src/services/settlement-reconciliation.ts +++ b/src/services/settlement-reconciliation.ts @@ -65,6 +65,7 @@ export async function reconcileSettlements( await reconcileSingleSettlement( { id: row.id, + groupId: row.groupId, stellarTxHash: row.stellarTxHash, retryCount: row.retryCount, shortCode: row.shortCode, @@ -94,6 +95,7 @@ export async function reconcileSettlements( */ export interface ReconcilableSettlement { id: string; + groupId: string; stellarTxHash: string | null; retryCount: number; /** Settlement short code, used to derive the expected memo (MP:). */ @@ -181,6 +183,7 @@ export async function reconcileSingleSettlement( }); emitEvent({ eventType: "settlement.failed", + groupId: settlement.groupId, payload: { settlementId: settlement.id, reason: err.message }, }); recLog.error( @@ -211,6 +214,7 @@ export async function reconcileSingleSettlement( }); emitEvent({ eventType: "settlement.completed", + groupId: settlement.groupId, payload: { settlementId: settlement.id, stellarTxHash: hash }, }); recLog.info({ id: settlement.id, hash }, "settlement completed"); @@ -233,6 +237,7 @@ export async function reconcileSingleSettlement( }); emitEvent({ eventType: "settlement.failed", + groupId: settlement.groupId, payload: { settlementId: settlement.id, reason: "transaction_failed" }, }); recLog.error({ id: settlement.id, hash }, "settlement transaction failed on Stellar"); From 6548f72678ae146f2cd356836de9d99476c76989 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:34:18 +0100 Subject: [PATCH 3/4] fix(webhooks): keep reconciliation test inputs compatible --- src/services/settlement-reconciliation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/settlement-reconciliation.ts b/src/services/settlement-reconciliation.ts index 3033af4..589a6e3 100644 --- a/src/services/settlement-reconciliation.ts +++ b/src/services/settlement-reconciliation.ts @@ -95,7 +95,7 @@ export async function reconcileSettlements( */ export interface ReconcilableSettlement { id: string; - groupId: string; + groupId?: string; stellarTxHash: string | null; retryCount: number; /** Settlement short code, used to derive the expected memo (MP:). */ From 9f7cf73c8d93f174bd08c30479491dafd215fce9 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem <150973162+zeemscript@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:08:42 +0100 Subject: [PATCH 4/4] fix(webhooks): dispatch confirmed settlement events --- src/services/event.ts | 1 + src/services/settlement-reconciliation.ts | 23 +++++++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/services/event.ts b/src/services/event.ts index 06ec2c5..7fbba5c 100644 --- a/src/services/event.ts +++ b/src/services/event.ts @@ -4,6 +4,7 @@ export const WEBHOOK_EVENT_TYPES = [ "expense.created", "expense.settled", "settlement.completed", + "settlement.confirmed", "settlement.failed", "treasury.proposal.created", "treasury.proposal.signed", diff --git a/src/services/settlement-reconciliation.ts b/src/services/settlement-reconciliation.ts index 589a6e3..1f5a431 100644 --- a/src/services/settlement-reconciliation.ts +++ b/src/services/settlement-reconciliation.ts @@ -11,7 +11,7 @@ import { verifyPaymentOperation, getTransactionPayments, } from "./horizonService"; -import { emitEvent } from "./event"; +import { dispatchEvent } from "./webhook"; const log = pino({ name: "settlement-reconciliation" }); @@ -181,11 +181,8 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash, reason: err.message }, }); - emitEvent({ - eventType: "settlement.failed", - groupId: settlement.groupId, - payload: { settlementId: settlement.id, reason: err.message }, - }); + void dispatchEvent("settlement.failed", { settlementId: settlement.id, reason: err.message }, settlement.groupId) + .catch(() => undefined); recLog.error( { id: settlement.id, hash, reason: err.message }, "settlement transaction verification failed" @@ -212,11 +209,8 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash }, }); - emitEvent({ - eventType: "settlement.completed", - groupId: settlement.groupId, - payload: { settlementId: settlement.id, stellarTxHash: hash }, - }); + void dispatchEvent("settlement.confirmed", { settlementId: settlement.id, stellarTxHash: hash }, settlement.groupId) + .catch(() => undefined); recLog.info({ id: settlement.id, hash }, "settlement completed"); } else { await applySettlementTransition({ @@ -235,11 +229,8 @@ export async function reconcileSingleSettlement( entityId: settlement.id, metadata: { stellarTxHash: hash, reason: "transaction_failed" }, }); - emitEvent({ - eventType: "settlement.failed", - groupId: settlement.groupId, - payload: { settlementId: settlement.id, reason: "transaction_failed" }, - }); + void dispatchEvent("settlement.failed", { settlementId: settlement.id, reason: "transaction_failed" }, settlement.groupId) + .catch(() => undefined); recLog.error({ id: settlement.id, hash }, "settlement transaction failed on Stellar"); } }