Generate a Java POJO from a Webhook Payload
Convert a generic webhook envelope (id, type, created, data) into a Java POJO. Learn how to model the polymorphic data field for different event types.
Detailed Explanation
Webhook Payloads in Java
Most webhook providers (Stripe, GitHub, Slack, Twilio) wrap event data in an envelope: an event id, a timestamp, an event type, and a data field whose shape depends on the type. The pattern is consistent enough that one generated POJO can serve as a starting point.
Generic Webhook Envelope
{
"id": "evt_1NXyz9abcdef",
"type": "invoice.paid",
"created": 1700000000,
"api_version": "2024-04-10",
"livemode": true,
"data": {
"object": {
"id": "in_1NXabc",
"amount_paid": 2500,
"currency": "usd",
"customer": "cus_NffrFeUfNV2Hib"
}
}
}
Generated POJOs
package com.example.webhooks;
import com.fasterxml.jackson.annotation.JsonProperty;
public class WebhookEvent {
private String id;
private String type;
private Long created;
@JsonProperty("api_version")
private String apiVersion;
private Boolean livemode;
private Data data;
// accessors
}
public class Data {
private Object object; // shape depends on event type
// accessors
}
Handling the Polymorphic data.object
The object field's shape changes with each event type. Three approaches:
1. Map<String, Object> (simplest)
private Map<String, Object> object;
Fast to set up; loses type safety.
2. JsonNode (Jackson tree model)
private JsonNode object;
Lets you query the tree with object.get("amount_paid").asInt() without committing to a fixed type.
3. Polymorphic deserialization (most rigorous)
Deserialize the envelope first, dispatch on type, then deserialize object into the correct concrete class:
WebhookEvent event = mapper.readValue(payload, WebhookEvent.class);
switch (event.getType()) {
case "invoice.paid":
Invoice invoice = mapper.convertValue(event.getData().getObject(), Invoice.class);
// process invoice
break;
case "customer.created":
Customer customer = mapper.convertValue(event.getData().getObject(), Customer.class);
// process customer
break;
}
See the Polymorphic Types example for the @JsonTypeInfo approach.
Verifying Webhook Signatures
Always verify the webhook signature before deserializing:
String signature = request.getHeader("Stripe-Signature");
String payload = request.getBody();
if (!verifySignature(payload, signature, webhookSecret)) {
throw new SecurityException("Invalid webhook signature");
}
WebhookEvent event = mapper.readValue(payload, WebhookEvent.class);
Use the provider's official SDK helper for signature verification — never implement HMAC comparison from scratch.
Idempotency
Webhook providers retry failed deliveries, so the same event id may arrive multiple times. Persist the event id and skip duplicates:
if (eventLog.exists(event.getId())) return; // already processed
processEvent(event);
eventLog.record(event.getId());
Use Case
Payment, communication, CI/CD, and CRM webhooks all need a typed envelope class plus per-event-type processing logic. Generating the envelope POJO is the first step toward a robust webhook ingestion service.