Webhooks and Callbacks
APIMatic's SDKs provide comprehensive support for webhooks and callbacks, enabling developers to handle asynchronous API events with type-safe processing and built-in security features. This capability simplifies the integration of event-driven workflows and real-time notifications in modern applications.
Webhooks and Callbacks Configuration in OpenAPI
Here is an example of how to define webhooks and callbacks in your OpenAPI specification:
paths:
/payments:
post:
summary: Create a payment
callbacks:
paymentCallback:
'{$request.body#/callbackUrl}':
post:
summary: Payment status callback
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
webhooks:
rejectedPayment:
...
verifiedPayment:
post:
summary: Verified payment status webhook
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
paymentId:
type: string
status:
type: string
...
For advanced configuration options including signature verification and custom grouping, APIMatic also supports webhook and callback OpenAPI extensions that provide enhanced functionality beyond the standard OpenAPI specification.
Usage Examples
- .NET
- Java
- Python
- Ruby
- TypeScript
[Route("webhooks")]
[ApiController]
public class WebhooksController : ControllerBase
{
// Use the provided handler to verify and parse the incoming event
private readonly PaymentHandler handler = new PaymentHandler("hmac-secret-key");
[HttpPost]
public async Task<IActionResult> ReceiveEvent()
{
// Create the HttpRequestData from the incoming HttpRequest
var eventRequest = HttpRequestData.FromAspNetCoreParams(
Request.Method,
Request.Scheme,
Request.Host.ToString(),
Request.Path.ToString(),
Request.QueryString.ToString(),
Request.Headers,
Request.Body,
Request.Query,
Request.Cookies,
Request.Protocol,
Request.ContentType,
Request.ContentLength
);
var eventParsingResult = await handler.VerifyAndParseEventAsync(eventRequest);
var result = eventParsingResult.MatchSome<string>(
verifiedPaymentEvent: payment => $"Payment verification received {payment}",
rejectedPaymentEvent: payment => $"Payment rejection received {payment}",
signatureVerificationFailed: error => $"Signature verification failed {error}",
unknown: () => "Unknown event received"
);
return Ok();
}
}
@RestController
public class WebhooksController {
@PostMapping("/webhooks")
public ResponseEntity<String> receiveEvent(
HttpServletRequest request,
@RequestBody(required = false) String body) {
// Create the HttpRequest from the incoming Request
HttpRequest httpRequest = HttpRequest.fromHttpServletRequest(
Collections.list(request.getHeaderNames()).stream().collect(Collectors.toMap(
h -> h,
h -> Collections.list(request.getHeaders(h))
)),
request.getParameterMap(),
request.getRequestURL(),
request.getQueryString(),
request.getMethod(),
body
);
// Use the provided handler to verify and parse the incoming event
PaymentHandler handler = new PaymentHandler("hmac-secret-key");
String result = handler.verifyAndParseEventAsync(httpRequest).thenApply(paymentParsingResult ->
paymentParsingResult.matchSome(new PaymentParsingResult.SomeCases<String>() {
@Override
public String verifiedPaymentEvent(VerifiedPaymentEvent verifiedPaymentEvent) {
// TODO: add handling logic
return "VerifiedPaymentEvent event received " + verifiedPaymentEvent;
}
@Override
public String rejectedPaymentEvent(RejectedPaymentEvent rejectedPaymentEvent) {
// TODO: add handling logic
return "RejectedPaymentEvent event received " + rejectedPaymentEvent;
}
@Override
public String unknown() {
// TODO: add unknown event handling
return "Unknown event received";
}
@Override
public String signatureVerificationFailed(SignatureVerificationResult signatureVerificationResult) {
// TODO: add signature verification failure handling
return "SignatureVerificationResult event received " + signatureVerificationResult;
}
})
).join();
return ResponseEntity.status(200).body(result);
}
}
app = Flask(__name__)
# Step 1: Create the handler with your shared secret key.
handler = PaymentHandler(secret_key="hmac-secret-key")
@app.route("/webhooks", methods=["POST"])
def webhooks():
# Step 2: Convert the incoming request using to_core_request (Django/Flask)
# or await to_core_request_async (FastAPI).
core_req = to_core_request(request)
# Step 3: Verify and parse the request into a typed event.
event = handler.verify_and_parse_event(core_req)
# Step 4: Pattern match on the event types and handle it.
if isinstance(event, VerifiedPaymentEvent):
print("Payment verification received")
# TODO: add handling logic
elif isinstance(event, RejectedPaymentEvent):
print("Payment rejection received")
# TODO: add handling logic
elif isinstance(event, SignatureVerificationFailure):
print("Signature verification failed")
# TODO: add signature verification failure handling
elif isinstance(event, UnknownEvent):
print("Unknown event")
# TODO: add unknown event handling
# Step 5: Return 200 OK to acknowledge receipt.
return Response(status=200)
# Define route
Rails.application.routes.draw do
post '/webhooks/receive', to: 'webhooks#receive'
end
# Define controller
class WebhooksController < ActionController::API
def receive
# Step 1: Create the handler with your shared secret key.
handler = PaymentHandler.new('your-shared-secret')
# Step 2: Use the Rails request directly (Rack::Request compatible).
event = handler.verify_and_parse_event(request)
# Step 3: Pattern match on the event types and handle it.
case event
when VerifiedPaymentEvent
puts 'Payment verification received'
# TODO: Add handling logic
when RejectedPaymentEvent
puts 'Payment rejection received'
# TODO: Add handling logic
when SignatureVerificationFailure
puts 'Signature verification failed'
# TODO: Add failure handling
when UnknownEvent
puts 'Unknown event received'
# TODO: Add unknown-event handling
else
# TODO: Add default handling
end
# Step 4: Return 200 OK to acknowledge receipt.
head :ok
end
end
// Create the handler with your shared secret key.
const handler = new PaymentHandler("hmac-secret-key");
// Define the webhook endpoint.
app.post("/webhooks", (req: Request, res: Response) => {
// Convert the incoming Express request into a core request.
const coreRequest = convertExpressRequest(req);
// Verify and parse the request into a typed event.
const event = handler.verifyAndParseEvent(coreRequest);
if (PaymentParsingResult.isVerifiedPaymentEvent(event)) {
console.log("Payment verification received");
// TODO: add handling logic
} else if (PaymentParsingResult.isRejectedPaymentEvent(event)) {
console.log("Payment rejection received");
// TODO: add handling logic
} else if (PaymentParsingResult.isSignatureVerificationFailure(event)) {
console.log("Signature verification failed");
// TODO: add signature verification failure handling
} else if (PaymentParsingResult.isEventTypeUnknown(event)) {
console.log("Unknown event");
// TODO: add unknown event handling
}
// Return 200 OK to acknowledge receipt.
res.status(200).send('OK');
});
Benefits
- Type-Safe Event Handling: Automatically generated handlers ensure type safety when processing webhook/callback events.
- Built-in Signature Verification: Support for HMAC-based signature verification and payload validation to ensure authenticity and integrity.
- Event Type Detection: Intelligent parsing and narrowing support multiple event or response types within the same endpoint.
- Simplified Integration: Eliminates the need for custom parsing and validation logic when working with asynchronous flows.
- Error Handling: Comprehensive error handling for malformed payloads, signature verification failures, and unknown event types.
Error Handling
The SDK provides built-in error handling for common issues encountered in both webhooks and callbacks:
Common Error Types
- Signature Verification Failure
This error occurs when:- The calculated HMAC signature doesn't match the signature provided in the headers
- The required headers are missing
- Unknown Event Type
This error occurs when:- The request body is empty
- The request body contains invalid or malformed JSON
- The request body doesn't match any known event
- The request body maps to more than one event