Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(backend): add webhooks openapi spec #1210

Merged
merged 12 commits into from
Mar 22, 2023
275 changes: 275 additions & 0 deletions packages/backend/src/openapi/webhooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
openapi: 3.1.0
info:
title: Rafiki Webhooks
version: 1.0.0
webhooks:
incomingPaymentCompleted:
post:
requestBody:
description: Notify account servicing entity that an incoming payment was completed and funds should now be withdrawn.
content:
application/json:
schema:
$ref: '#/components/schemas/incomingPayment'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
incomingPaymentExpired:
post:
requestBody:
description: Notify account servicing entity that an incoming payment has expired and funds should now be withdrawn.
content:
application/json:
schema:
$ref: '#/components/schemas/incomingPayment'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
outgoingPaymentCreated:
post:
requestBody:
description: Notify account servicing entity that an outgoing payment was created and should now be funded.
content:
application/json:
schema:
$ref: '#/components/schemas/outgoingPayment'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
outgoingPaymentCompleted:
post:
requestBody:
description: Notify account servicing entity that an outgoing payment completed. Remaining funds should be withdrawn.
content:
application/json:
schema:
$ref: '#/components/schemas/outgoingPayment'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
outgoingPaymentFailed:
post:
requestBody:
description: Notify account servicing entity that an outgoing payment failed and won't be retried. Funds should be withdrawn.
content:
application/json:
schema:
$ref: '#/components/schemas/outgoingPayment'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
webMonetization:
post:
requestBody:
description: Notify account servicing entity that a Web Monetization payment was received and funds should be withdrawn.
content:
application/json:
schema:
$ref: '#/components/schemas/webMonetization'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully

components:
schemas:
incomingPayment:
required:
- id
- type
- attempts
- processAt
- data
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- incoming_payment.completed
- incoming_payment.expired
attempts:
type: integer
processAt:
type: string
format: date-time
data:
type: object
required:
- id
- paymentPointerId
- createdAt
- updatedAt
- expiresAt
- receivedAmount
- completed
properties:
id:
type: string
format: uuid
paymentPointerId:
type: string
format: uuid
completed:
type: boolean
incomingAmount:
$ref: ./schemas.yaml#/components/schemas/amount
receivedAmount:
$ref: ./schemas.yaml#/components/schemas/amount
description:
type: string
externalRef:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
statusCode:
type: integer
withdrawal:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we necessarily need withdrawal? it's not being used in the MAP webhooks handler, and looking at the webhook publishers, the only information that isn't already on the data is the assetId: something that we could just include in the data object as well IMO

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's only used by the outgoing payment event.
@wilsonianb can you explain its use?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withdrawal isn't included in the request, just id, type, and data

const body = {
id: event.id,
type: event.type,
data: event.data
}
if (deps.config.signatureSecret) {
requestHeaders['Rafiki-Signature'] = generateWebhookSignature(
body,
deps.config.signatureSecret,
deps.config.signatureVersion
)
}
await axios.post(deps.config.webhookUrl, body, {
timeout: deps.config.webhookTimeout,
headers: requestHeaders,
validateStatus: (status) => status === 200
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at this: https://github.com/interledger/rafiki/blob/main/packages/backend/src/webhook/model.ts#L8

Is it just id, type, and data for all of them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think the webhook service code I linked 👆 is the only place we're sending webhook requests

Copy link
Member Author

@sabineschaller sabineschaller Mar 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about here? I think we have two different sendWebhookEvents (which we should clean up)

const handleCompleted = async (
deps: ServiceDependencies,
payment: OutgoingPayment
): Promise<void> => {
await payment.$query(deps.knex).patch({
state: OutgoingPaymentState.Completed
})
await sendWebhookEvent(deps, payment, PaymentEventType.PaymentCompleted)
}
export const sendWebhookEvent = async (
deps: ServiceDependencies,
payment: OutgoingPayment,
type: PaymentEventType
): Promise<void> => {
// Tigerbeetle accounts are only created as the OutgoingPayment is funded.
// So default the amountSent and balance to 0 for outgoing payments still in the funding state
const amountSent =
payment.state === OutgoingPaymentState.Funding
? BigInt(0)
: await deps.accountingService.getTotalSent(payment.id)
const balance =
payment.state === OutgoingPaymentState.Funding
? BigInt(0)
: await deps.accountingService.getBalance(payment.id)
if (amountSent === undefined || balance === undefined) {
throw LifecycleError.MissingBalance
}
const withdrawal = balance
? {
accountId: payment.id,
assetId: payment.assetId,
amount: balance
}
: undefined
await PaymentEvent.query(deps.knex).insertAndFetch({
type,
data: payment.toData({ amountSent, balance }),
withdrawal
})
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one ^ just adds it to the DB to prepare for sending. It looks the event.withdrawal doesn't gets added to the webhook POST request (the one that @wilsonianb commented), since body just has id type data.

Maybe we can even just remote withdrawal from the code completely on a follow-up PR?

$ref: '#/components/schemas/withdrawal'
outgoingPayment:
required:
- id
- type
- attempts
- processAt
- data
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- outgoing_payment.created
- outgoing_payment.completed
- outgoing_payment.failed
attempts:
type: integer
processAt:
type: string
format: date-time
data:
type: object
required:
- id
- paymentPointerId
- createdAt
- updatedAt
- expiresAt
- sendAmount
- receiveAmount
- state
- stateAttempts
- balance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- balance
- balance
- receiver

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

properties:
id:
type: string
format: uuid
paymentPointerId:
type: string
format: uuid
state:
type: string
enum:
- FUNDING
- SENDING
- FAILED
- COMPLETED
sendAmount:
$ref: ./schemas.yaml#/components/schemas/amount
sentAmount:
$ref: ./schemas.yaml#/components/schemas/amount
description:
type: string
externalRef:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
error:
type: string
stateAttempts:
type: integer
balance:
type: string
format: uint64
peerId:
type: string
format: uuid
statusCode:
type: integer
withdrawal:
$ref: '#/components/schemas/withdrawal'
webMonetization:
required:
- id
- type
- attempts
- processAt
- data
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- payment_pointer.web_monetization
attempts:
type: integer
processAt:
type: string
format: date-time
data:
type: object
required:
- paymentPointer
properties:
paymentPointer:
type: object
required:
- id
- createdAt
- received
properties:
id:
type: string
format: uuid
createdAt:
type: string
format: date-time
received:
type: string
format: uint64
statusCode:
type: integer
withdrawal:
$ref: '#/components/schemas/withdrawal'
withdrawal:
type: object
required:
- accountId: string
- assetId: string
- amount: bigint
properties:
accountId:
type: string
format: uuid
assetId:
type: string
format: uuid
amount:
type: integer