Normally, lift will look for a "view" that satifying an HTTP request. A view is an XHTML page or segment of a page with special <lift:xxx> processing instructions in it (e.g., an instruction to embed a controller, an instruction to surround the XHTML with another template, etc.) However, in certain cases, for example, when you want to satisfy a Web Services (REST) request, you can use Scala's pattern matching to intercept a request and service it with a "short lived" controller (rather than lift's normal long-lived, Actor-based controllers.)

It's easy to "dispatch" incoming HTTP requests. In your "Boot" class create a PartialFunction that matches a pattern related to the incoming request and then create a short-lived controller to service the request.

/**
  * A class that's instantiated early and run.  It allows the application
  * to modify lift's environment
  */
class Boot {
  def boot {
    
    val dispatcher: PartialFunction[{RequestState, List[String],
      (String) => java.io.InputStream}, 
      (HttpServletRequest) => Option[Any]] = {
    case {r, "webservices" :: c :: Nil, _} => { 
          (req: HttpServletRequest) => {
          val rc = new WebServices(r, req)
          val invoker = createInvoker(c, rc)
          invoker match {
            case None => None
            case Some(f) => f()
          }
          }
        }
    }
    Servlet.addBefore(dispatcher)
  }
}

This code matches all the requests to "/webservices/????" and assigns the stuff in '????' to the variable c. Then the code attempts to find a public method with that name on the controller. If the method exists, it's invoked and lift processes the result.

You can see a couple of examples:
All Users in the database
Add a User to the database

  def add_user: XmlResponse = {
    var success = false
    for (val firstname <- params("firstname");
         val lastname <- params("lastname");
         val email <- params("email")) {
      val u = new User
      u.firstName := firstname
      u.lastName := lastname
      u.email := email
      params("textarea").map{v => u.textArea := v}
      params("password").map{v => u.password := v}
      success = u.save
    }
    
    XmlResponse(<add_user success={success.toString}/>)
  }

The controller simply validates that the required parameters exist (the for block.) If they do, they are assigned, the optional parameters are assigned, and the user is saved.