@@ -155,6 +155,20 @@ export interface RouteConfig {
155155 */
156156export type RoutesConfig = Record < string , RouteConfig > | RouteConfig ;
157157
158+ /**
159+ * Hook that runs on every request to a protected route, before payment processing.
160+ * Can grant access without payment, deny the request, or continue to payment flow.
161+ *
162+ * @returns
163+ * - `void` - Continue to payment processing (default behavior)
164+ * - `{ grantAccess: true }` - Grant access without requiring payment
165+ * - `{ abort: true; reason: string }` - Deny the request (returns 403)
166+ */
167+ export type ProtectedRequestHook = (
168+ context : HTTPRequestContext ,
169+ routeConfig : RouteConfig ,
170+ ) => Promise < void | { grantAccess : true } | { abort : true ; reason : string } > ;
171+
158172/**
159173 * Compiled route for efficient matching
160174 */
@@ -260,6 +274,7 @@ export class x402HTTPResourceServer {
260274 private compiledRoutes : CompiledRoute [ ] = [ ] ;
261275 private routesConfig : RoutesConfig ;
262276 private paywallProvider ?: PaywallProvider ;
277+ private protectedRequestHooks : ProtectedRequestHook [ ] = [ ] ;
263278
264279 /**
265280 * Creates a new x402HTTPResourceServer instance.
@@ -287,6 +302,24 @@ export class x402HTTPResourceServer {
287302 }
288303 }
289304
305+ /**
306+ * Get the underlying x402ResourceServer instance.
307+ *
308+ * @returns The underlying x402ResourceServer instance
309+ */
310+ get server ( ) : x402ResourceServer {
311+ return this . ResourceServer ;
312+ }
313+
314+ /**
315+ * Get the routes configuration.
316+ *
317+ * @returns The routes configuration
318+ */
319+ get routes ( ) : RoutesConfig {
320+ return this . routesConfig ;
321+ }
322+
290323 /**
291324 * Initialize the HTTP resource server.
292325 *
@@ -325,6 +358,18 @@ export class x402HTTPResourceServer {
325358 return this ;
326359 }
327360
361+ /**
362+ * Register a hook that runs on every request to a protected route, before payment processing.
363+ * Hooks are executed in order of registration. The first hook to return a non-void result wins.
364+ *
365+ * @param hook - The request hook function
366+ * @returns The x402HTTPResourceServer instance for chaining
367+ */
368+ onProtectedRequest ( hook : ProtectedRequestHook ) : this {
369+ this . protectedRequestHooks . push ( hook ) ;
370+ return this ;
371+ }
372+
328373 /**
329374 * Process HTTP request and return response instructions
330375 * This is the main entry point for framework middleware
@@ -345,6 +390,24 @@ export class x402HTTPResourceServer {
345390 return { type : "no-payment-required" } ; // No payment required for this route
346391 }
347392
393+ // Execute request hooks before any payment processing
394+ for ( const hook of this . protectedRequestHooks ) {
395+ const result = await hook ( context , routeConfig ) ;
396+ if ( result && "grantAccess" in result ) {
397+ return { type : "no-payment-required" } ;
398+ }
399+ if ( result && "abort" in result ) {
400+ return {
401+ type : "payment-error" ,
402+ response : {
403+ status : 403 ,
404+ headers : { "Content-Type" : "application/json" } ,
405+ body : { error : result . reason } ,
406+ } ,
407+ } ;
408+ }
409+ }
410+
348411 // Normalize accepts field to array of payment options
349412 const paymentOptions = this . normalizePaymentOptions ( routeConfig ) ;
350413
0 commit comments