diff --git a/docs/start-building/webhooks/using-webhooks.mdx b/docs/start-building/webhooks/using-webhooks.mdx
index eff0599..898d599 100644
--- a/docs/start-building/webhooks/using-webhooks.mdx
+++ b/docs/start-building/webhooks/using-webhooks.mdx
@@ -315,6 +315,135 @@ func main() {
```
+
+
+
+
+```dart showLineNumbers
+import 'dart:io';
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:blockfrost_api/blockfrost_api.dart';
+import 'package:blockfrost_secure_webhooks/webhook_handler.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_router/shelf_router.dart';
+
+// You will find your webhook secret auth token in your webhook settings in the Blockfrost Dashboard
+// Pass it as environment variable when starting the server:
+// BLOCKFROST_TOKEN='WEBHOOK-AUTH-TOKEN' dart run bin/server.dart
+const String secretAuthEnvToken = 'BLOCKFROST_TOKEN';
+const int port = 8080;
+
+// Main Method
+void main() async {
+ final secretToken = Platform.environment[secretAuthEnvToken];
+ if (secretToken == null || secretToken.isEmpty) {
+ print('FATAL: Missing environment variable $secretAuthEnvToken');
+ exit(1);
+ }
+
+ // path mapping with router configuration
+ final router = Router()
+ ..post(
+ '/webhook',
+ (Request request) => handleWebhook(
+ request: request,
+ secretToken: secretToken,
+ validator:
+ BlockfrostSignatureValidator())) // Map POST requests to /webhook
+ ..get('/status', (_) => Response.ok('ok')); // Simple test route
+
+ // Configure middleware (optional, but good practice for logging)
+ final handler =
+ Pipeline().addMiddleware(logRequests()).addHandler(router.call);
+
+ // Start the server
+ try {
+ final server = await io.serve(handler, InternetAddress.anyIPv4, port);
+ print('Server running on http://${server.address.host}:${server.port}');
+ print('Ready to receive webhooks at: http://localhost:$port/webhook');
+ } catch (e) {
+ print('Failed to start server: $e');
+ }
+}
+
+/// Request handler which handles the incoming webhook
+Future handleWebhook(
+ {required Request request,
+ required String secretToken,
+ required SignatureValidator validator}) async {
+ final String requestPayload = await request.readAsString();
+ final signatureHeader = request.headers['blockfrost-signature'];
+
+ // Verify request data is available
+ if (signatureHeader == null || signatureHeader.isEmpty) {
+ return Response(400, body: 'Missing signature header.');
+ }
+ if (requestPayload.isEmpty) {
+ return Response(400, body: 'Empty requestPayload.');
+ }
+
+ // Validate using the SignatureValidator
+ try {
+ validator.validate(
+ requestPayload: requestPayload,
+ signatureHeader: signatureHeader,
+ secretAuthToken: secretToken);
+ } on SignatureValidationException catch (e) {
+ return Response(
+ 400,
+ body: 'Signature validation failed! ${e.toString()}',
+ );
+ } catch (e) {
+ return Response.internalServerError(body: 'Signature validation failed!');
+ }
+
+ // Parsing requestPayload JSON
+ try {
+ Map data =
+ jsonDecode(requestPayload) as Map;
+ final String? type = data['type'] as String?;
+ final dynamic payload = data['payload'];
+
+ if (type == null || payload == null) {
+ throw Exception("Error: Payload or type is missing.");
+ }
+
+ // process event types (transaction, block, delegation, epoch)
+ switch (type) {
+ case "transaction":
+ final List transactions = payload as List;
+ print("Received ${transactions.length} transactions");
+ for (final transaction in transactions) {
+ final Map txData =
+ transaction as Map;
+ final String? txHash =
+ (txData['tx'] as Map?)?['hash'] as String?;
+ print("Transaction $txHash");
+ }
+ break;
+ case "block":
+ final Map blockData = payload as Map;
+ final String? blockHash = blockData['hash'] as String?;
+ print("Received block hash $blockHash");
+ break;
+ // ...other types (delegation, epoch)
+ default:
+ throw Exception("Unexpected type $type");
+ }
+ // Signature is valid
+ return Response.ok(
+ jsonEncode({'status': 'Webhook received successfully ✅'}),
+ headers: {'content-type': 'application/json'},
+ );
+ } catch (e) {
+ return Response.internalServerError(body: 'Failed to process JSON.');
+ }
+}
+```
+
### Retries
diff --git a/docs/start-building/webhooks/webhooks-signatures.mdx b/docs/start-building/webhooks/webhooks-signatures.mdx
index c5b7742..21a6eb2 100644
--- a/docs/start-building/webhooks/webhooks-signatures.mdx
+++ b/docs/start-building/webhooks/webhooks-signatures.mdx
@@ -142,6 +142,106 @@ func main() {
+
+
+```dart showLineNumbers
+import 'dart:io';
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:blockfrost_api/blockfrost_api.dart';
+import 'package:blockfrost_secure_webhooks/webhook_handler.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_router/shelf_router.dart';
+
+// You will find your webhook secret auth token in your webhook settings in the Blockfrost Dashboard
+// Pass it as environment variable when starting the server:
+// BLOCKFROST_TOKEN='WEBHOOK-AUTH-TOKEN' dart run bin/server.dart
+const String secretAuthEnvToken = 'BLOCKFROST_TOKEN';
+const int port = 8080;
+
+// Main Method
+void main() async {
+ final secretToken = Platform.environment[secretAuthEnvToken];
+ if (secretToken == null || secretToken.isEmpty) {
+ print('FATAL: Missing environment variable $secretAuthEnvToken');
+ exit(1); // Exit if secret is not set
+ }
+
+ // path mapping with router configuration
+ final router = Router()
+ ..post(
+ '/webhook',
+ (Request request) => handleWebhook(
+ request: request,
+ secretToken: secretToken,
+ validator:
+ BlockfrostSignatureValidator())) // Map POST requests to /webhook
+ ..get('/status', (_) => Response.ok('ok')); // Simple test route
+
+ // Configure middleware (optional, but good practice for logging)
+ final handler =
+ Pipeline().addMiddleware(logRequests()).addHandler(router.call);
+
+ // Start the server
+ try {
+ final server = await io.serve(handler, InternetAddress.anyIPv4, port);
+ print('Server running on http://${server.address.host}:${server.port}');
+ print('Ready to receive webhooks at: http://localhost:$port/webhook');
+ } catch (e) {
+ print('Failed to start server: $e');
+ }
+}
+
+/// Request handler which handles the incoming webhook
+Future handleWebhook(
+ {required Request request,
+ required String secretToken,
+ required SignatureValidator validator}) async {
+ final String requestPayload = await request.readAsString();
+ final signatureHeader = request.headers['blockfrost-signature'];
+
+ // Verify request data is available
+ if (signatureHeader == null || signatureHeader.isEmpty) {
+ return Response(400, body: 'Missing signature header.');
+ }
+ if (requestPayload.isEmpty) {
+ return Response(400, body: 'Empty requestPayload.');
+ }
+
+ // Validate using the SignatureValidator
+ try {
+ validator.validate(
+ requestPayload: requestPayload,
+ signatureHeader: signatureHeader,
+ secretAuthToken: secretToken);
+ } on SignatureValidationException catch (e) {
+ return Response(
+ 400,
+ body: 'Signature validation failed! ${e.toString()}',
+ );
+ } catch (e) {
+ return Response.internalServerError(body: 'Signature validation failed!');
+ }
+
+ // Try to parse payload JSON
+ try {
+ jsonDecode(requestPayload) as Map;
+ } catch (e) {
+ return Response.internalServerError(body: 'Failed to process JSON.');
+ }
+
+ // Signature is valid
+ return Response.ok(
+ jsonEncode({'status': 'Webhook received successfully ✅'}),
+ headers: {'content-type': 'application/json'},
+ );
+}
+```
+
+
+
## Verifying the signature manually
diff --git a/docusaurus.config.js b/docusaurus.config.js
index f4f990b..2ca822c 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -117,6 +117,9 @@ module.exports = {
],
copyright: `Copyright © ${new Date().getFullYear()} Blockfrost.io`,
},
+ prism: {
+ additionalLanguages: ['dart'],
+ },
},
presets: [
[