You get the http.Request passed to your ServeHTTP function, which is where the magic happens, so you can do a lot of inspection here based on what the http standard library gives you (see: http package - net/http - pkg.go.dev).
Just to elaborate a bit further, we benefit from being able to use Golang’s standard http library. This library is incredibly widely used and incredibly widely documented, with tutorials everywhere. Just the godoc alone (see: http package - net/http - pkg.go.dev) is incredibly useful.
So the first few you’re looking for - definitely aren’t specific to Caddy at all. There’s much better literature out there explaining how to interact with those, and we’d be doing their authors and our readers a disservice by trying to recreate those guides and tutorials. As for these parts:
To be clear, there’s no response already built for you when your ServeHTTP function receives the request. It gets the http.Request and a http.ResponseWriter. You can write whatever you want to the ResponseWriter - you are 100% in charge. It’s entirely up to you how you do it - some conventional way (the tutorials linked above are great), or by importing other libs that help with the process, it’s your call. You can do almost literally anything. There’s nothing to modify or change. You get a blank slate.
You want to respond differently if a request is not valid? Time to throw in some conditional statements. Build your “invalid request” response and decide how you want to handle a “valid” request (maybe pass it along to the next handler?).