Minka Ledger Docs
How To Guides

How to handle effect webhook calls


In this example we will implement simple HTTP server in Node.js express which will handle the call to the webhook url defined in an effect registered previously.

We will receive an event when balance is received to a wallet as a result of successfully cleared intent which contains a claim targeting the wallet (either issue or transfer claim). Ledger will raise an event with balance-received signal for each wallet which receives the balance. Event will be forwarded by the registered effect to our HTTP server with event body containing:

  • handle - unique identifier of the event, used for idempotency
  • signal - signal of the event, string
  • amount - amount of balance received
  • wallet - wallet record which received the balance
  • symbol - symbol record of the received balance
  • intent - intent record which caused balance change
{
	"hash": "<event-hash>",
	"data": {
		"handle": "evt_vOcNMfFXQq3T4G6rz",
		"signal": "balance-received",
		"amount": 100,
		"wallet": {
			"hash": "<wallet-hash>",
			"data": {
				"handle": "bank1",
				...
			},
			"meta": { ... }
		},
		"symbol": { ... },
		"intent": { ... }
	},
	"meta": { ... }
}

We will verify the event payload and execute side-effect in external system. This side-effect can, for example, execute the transaction in banking core, send email to user, or even send new intent to the Ledger. We will use handle for idempotency check to ensure that the same event is not already processed (it can happen with retries mechanism if the ledger didn’t receive success response due to network error).

import express from 'express'
 
const app = express()
const port = 3000
 
const BANK_NAME = 'bank1'
const SYMBOL_NAME = 'usd'
const SIGNAL_BALANCE_RECEIVED = 'balance-received'
 
const sdk = new LedgerSdk({
	server: '<your ledger URL>',
  signer: {
		format: 'ed25519-raw',
		public: '<your ledger public key>'
	}
})
 
/**
 * LedgerSdk.proofs returns a new instance of a
 * verification client which can be used to verify
 * proofs coming from the ledger. By default it 
 * verifies that records have at least 1 proof, by
 * calling the ledger() method, we also verify that
 * the response is signed by the expected ledger key
*/
const verificationClient = sdk.proofs.ledger()
 
app.post('/balance-received', async (req, res) => {
  // 1. Acknowledge the event by sendng HTTP 202 (ACCEPTED) in response
  console.log(`Event received, acknowledging..`)
  res.status(202).send()
 
  // 2. Verify that event is signed by expected ledger key
	console.log(`Verifying event...`)
	const event = req.body
	await verificationClient.verify(event)
 
	// 3. Idempotency chech, verify that event is not already processed
	if (isEventProcessed(event?.data?.handle)) {
		// Event already processed
		return
	}
  
  
	// 4. Verify that signal, symbol and wallet are correct
	const signal = event?.data?.signal
	if (signal !== SIGNAL_BALANCE_RECEIVED) {
		// Wrong signal sent
		console.error(`Wrong signal '${signal}', expected '${SIGNAL_BALANCE_RECEIVED}'`)
		return
	}
	const walletHandle = event?.data?.wallet?.data?.handle
	if (walletHandle !== BANK_NAME) {
		// Wrong wallet
		console.error(`Wrong target wallet '${walletHandle}', expected '${BANK_NAME}'`)
		return
	}
	const symbolHandle = event?.data?.symbol?.data?.handle
	if (symbolHandle !== SYMBOL_NAME) {
		// Wrong symbol
		console.error(`Wrong symbol '${symbolHandle}', expected '${SYMBOL_NAME}'`)
		return
	}
 
  // 5. Process the intent which caused balance movement
	await coreProcessIntent(event?.data?.intent)
  
})
 
app.listen(port, () => {
  console.log(`Server running on port ${port}`)
})

Webhook handler should acknowledge that event is delivered as soon as possible by responding with a success HTTP status code (2XX) and perform internal processing after that. Every other response status code including the network error will be considered by Ledger as failed delivery and it will retry it with exponential backoff, or stop retrying only in case of the response status code being 501 Not Implemented. The exponential backoff uses a 20% delay step increase (1s, 1.2s, 1.44s, etc., with a maximum of 1 hour) until a confirmation in the shape of a success status code is received in the http response.

Webhook handler should verify that the event payload is signed with a known ledger key. It is a proof that it originates from this ledger instance and is not changed in transport. For that the handler should keep the public key of the ledger and use it for verification as shown in code snippet.

const sdk = new LedgerSdk({
  server: '<your ledger URL>',
  signer: {
    format: 'ed25519-raw',
    public: '<your ledger public key>'
  }
})
const verificationClient = sdk.proofs.ledger()
await verificationClient.verify(event)

How to register an effect

On this page