Skip to content

Webhooks

Webhooks provide a way to receive HTTP callbacks when events occur in your inbox. Instead of polling or maintaining SSE connections, your application receives push notifications automatically.

Create a webhook for an inbox to receive notifications when emails arrive:

Inbox inbox = client.createInbox();
WebhookData webhook = inbox.createWebhook(
"https://your-app.com/webhook/emails",
List.of(WebhookEventType.EMAIL_RECEIVED)
);
System.out.println("Webhook ID: " + webhook.getId());
System.out.println("Secret: " + webhook.getSecret()); // Save this for signature verification

For more options, use the CreateWebhookRequest builder:

CreateWebhookRequest request = CreateWebhookRequest.builder()
.url("https://your-app.com/webhook/emails")
.events(WebhookEventType.EMAIL_RECEIVED)
.description("Production email notifications")
.build();
WebhookData webhook = inbox.createWebhook(request);
PropertyTypeRequiredDescription
urlStringYesThe URL to send webhook requests to
eventsList<WebhookEventType>YesEvents that trigger the webhook
templateString or ObjectNoPayload format template
filterFilterConfigNoFilter which emails trigger the webhook
descriptionStringNoHuman-readable description (max 500)
EventDescription
WebhookEventType.EMAIL_RECEIVEDEmail received by the inbox
WebhookEventType.EMAIL_STOREDEmail successfully stored
WebhookEventType.EMAIL_DELETEDEmail deleted from inbox
List<WebhookData> webhooks = inbox.listWebhooks();
System.out.println("Total webhooks: " + webhooks.size());
for (WebhookData webhook : webhooks) {
System.out.printf("- %s: %s (%s)%n",
webhook.getId(),
webhook.getUrl(),
webhook.isEnabled() ? "enabled" : "disabled");
}
WebhookData webhook = inbox.getWebhook("whk_webhook_id");
System.out.println("URL: " + webhook.getUrl());
System.out.println("Events: " + webhook.getEvents());
System.out.println("Created: " + webhook.getCreatedAt());
System.out.println("Last delivery: " + webhook.getLastDeliveryAt());
WebhookStats stats = webhook.getStats();
if (stats != null) {
System.out.printf("Deliveries: %d/%d (%.1f%% success)%n",
stats.getSuccessfulDeliveries(),
stats.getTotalDeliveries(),
stats.getSuccessRate());
}
UpdateWebhookRequest update = UpdateWebhookRequest.builder()
.url("https://your-app.com/webhook/v2/emails")
.enabled(true)
.description("Updated webhook endpoint")
.build();
WebhookData updated = inbox.updateWebhook("whk_webhook_id", update);
UpdateWebhookRequest update = UpdateWebhookRequest.builder()
.enabled(false)
.build();
inbox.updateWebhook("whk_webhook_id", update);
inbox.deleteWebhook("whk_webhook_id");
System.out.println("Webhook deleted");

Use filters to control which emails trigger webhooks:

FilterConfig filter = FilterConfig.builder()
.addRule(FilterableField.FROM_ADDRESS, FilterOperator.DOMAIN, "example.com")
.addRule(FilterableField.SUBJECT, FilterOperator.CONTAINS, "Invoice")
.mode(FilterMode.ALL) // ALL = AND, ANY = OR
.build();
CreateWebhookRequest request = CreateWebhookRequest.builder()
.url("https://your-app.com/webhook/emails")
.events(WebhookEventType.EMAIL_RECEIVED)
.filter(filter)
.build();
WebhookData webhook = inbox.createWebhook(request);
FieldDescription
FilterableField.SUBJECTEmail subject line
FilterableField.FROM_ADDRESSSender email address
FilterableField.FROM_NAMESender display name
FilterableField.TO_ADDRESSFirst recipient email address
FilterableField.TO_NAMEFirst recipient display name
FilterableField.BODY_TEXTPlain text body (first 5KB)
FilterableField.BODY_HTMLHTML body (first 5KB)
OperatorDescriptionExample Value
FilterOperator.EQUALSExact match[email protected]
FilterOperator.CONTAINSContains substringReset
FilterOperator.STARTS_WITHStarts with stringRE:
FilterOperator.ENDS_WITHEnds with string@company.com
FilterOperator.DOMAINEmail domain matchexample.com
FilterOperator.REGEXRegular expression matchOrder #\\d+
FilterOperator.EXISTSField exists and is non-emptytrue

By default, matching is case-insensitive. To enable case-sensitive matching:

FilterRule rule = new FilterRule(
FilterableField.SUBJECT,
FilterOperator.CONTAINS,
"URGENT",
true // caseSensitive
);
FilterConfig filter = FilterConfig.builder()
.addRule(rule)
.mode(FilterMode.ALL)
.build();

Only trigger webhooks for authenticated emails:

FilterConfig filter = FilterConfig.builder()
.mode(FilterMode.ALL)
.requireAuth(true) // Only emails passing SPF/DKIM/DMARC
.build();
CreateWebhookRequest request = CreateWebhookRequest.builder()
.url("https://your-app.com/webhook/verified-emails")
.events(WebhookEventType.EMAIL_RECEIVED)
.filter(filter)
.build();

Templates control the webhook payload format.

// Slack-formatted payload
CreateWebhookRequest slackRequest = CreateWebhookRequest.builder()
.url("https://hooks.slack.com/services/...")
.events(WebhookEventType.EMAIL_RECEIVED)
.template("slack")
.build();
WebhookData slackWebhook = inbox.createWebhook(slackRequest);
// Discord-formatted payload
CreateWebhookRequest discordRequest = CreateWebhookRequest.builder()
.url("https://discord.com/api/webhooks/...")
.events(WebhookEventType.EMAIL_RECEIVED)
.template("discord")
.build();
WebhookData discordWebhook = inbox.createWebhook(discordRequest);
// Microsoft Teams
CreateWebhookRequest teamsRequest = CreateWebhookRequest.builder()
.url("https://outlook.office.com/webhook/...")
.events(WebhookEventType.EMAIL_RECEIVED)
.template("teams")
.build();
WebhookData teamsWebhook = inbox.createWebhook(teamsRequest);

Available templates: slack, discord, teams, simple, notification, zapier, default

String templateBody = """
{
"email_id": "{{id}}",
"sender": "{{data.from.address}}",
"subject_line": "{{data.subject}}",
"received_timestamp": "{{timestamp}}"
}
""";
CustomTemplate customTemplate = new CustomTemplate(templateBody, "application/json");
CreateWebhookRequest request = CreateWebhookRequest.builder()
.url("https://your-app.com/webhook/emails")
.events(WebhookEventType.EMAIL_RECEIVED)
.template(customTemplate)
.build();
WebhookData webhook = inbox.createWebhook(request);
VariableDescription
{{id}}Event ID
{{type}}Event type
{{createdAt}}Unix timestamp
{{timestamp}}ISO 8601 timestamp
{{data.from.address}}Sender email
{{data.from.name}}Sender name
{{data.subject}}Email subject
{{data.snippet}}Email preview
{{data.inboxEmail}}Inbox address
TestWebhookResponse result = inbox.testWebhook("whk_webhook_id");
if (result.isSuccess()) {
System.out.println("Test successful!");
System.out.println("Status: " + result.getStatusCode());
System.out.println("Response time: " + result.getResponseTime() + "ms");
} else {
System.err.println("Test failed: " + result.getError());
}
PropertyTypeDescription
successbooleanWhether the test succeeded
statusCodeIntegerHTTP status code from endpoint
responseTimeLongResponse time in milliseconds
responseBodyStringResponse body (truncated to 1KB)
errorStringError message if test failed
payloadSentObjectTest payload that was sent

Rotate webhook secrets periodically for security:

RotateSecretResponse result = inbox.rotateWebhookSecret("whk_webhook_id");
System.out.println("New secret: " + result.getSecret());
System.out.println("Old secret valid until: " + result.getPreviousSecretValidUntil());
// Update your application with the new secret
// The old secret remains valid during the 1-hour grace period

Always verify webhook signatures in your endpoint. Webhooks include the following headers:

HeaderDescription
X-Vault-SignatureHMAC-SHA256 signature
X-Vault-TimestampUnix timestamp
X-Vault-EventEvent type
X-Vault-DeliveryUnique delivery ID

The signature is computed over ${timestamp}.${raw_request_body}:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
public class WebhookVerifier {
public static boolean verifySignature(
String rawBody,
String signature,
String timestamp,
String secret) {
try {
String signedPayload = timestamp + "." + rawBody;
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
hmac.init(keySpec);
byte[] hash = hmac.doFinal(signedPayload.getBytes(StandardCharsets.UTF_8));
String expectedSignature = "sha256=" + bytesToHex(hash);
return MessageDigest.isEqual(
signature.getBytes(StandardCharsets.UTF_8),
expectedSignature.getBytes(StandardCharsets.UTF_8)
);
} catch (Exception e) {
return false;
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
public class WebhookController {
private static final String WEBHOOK_SECRET = System.getenv("WEBHOOK_SECRET");
@PostMapping("/webhook/emails")
public ResponseEntity<String> handleWebhook(
@RequestBody String rawBody,
@RequestHeader("X-Vault-Signature") String signature,
@RequestHeader("X-Vault-Timestamp") String timestamp) {
if (!WebhookVerifier.verifySignature(rawBody, signature, timestamp, WEBHOOK_SECRET)) {
return ResponseEntity.status(401).body("Invalid signature");
}
// Process the webhook
System.out.println("Email received: " + rawBody);
return ResponseEntity.ok("OK");
}
}
import com.vaultsandbox.client.exception.WebhookNotFoundException;
import com.vaultsandbox.client.exception.InboxNotFoundException;
import com.vaultsandbox.client.exception.ApiException;
try {
WebhookData webhook = inbox.getWebhook("whk_webhook_id");
} catch (WebhookNotFoundException e) {
System.err.println("Webhook not found");
} catch (InboxNotFoundException e) {
System.err.println("Inbox not found");
} catch (ApiException e) {
System.err.printf("API error (%d): %s%n", e.getStatusCode(), e.getMessage());
}
import com.vaultsandbox.client.*;
import com.vaultsandbox.client.model.*;
import java.util.List;
public class WebhookExample {
public static void main(String[] args) {
ClientConfig config = ClientConfig.builder()
.apiKey(System.getenv("VAULTSANDBOX_API_KEY"))
.baseUrl(System.getenv("VAULTSANDBOX_URL"))
.build();
try (VaultSandboxClient client = VaultSandboxClient.create(config)) {
// Create inbox
Inbox inbox = client.createInbox();
System.out.println("Inbox: " + inbox.getEmailAddress());
// Create webhook with filter
FilterConfig filter = FilterConfig.builder()
.addRule(FilterableField.FROM_ADDRESS, FilterOperator.DOMAIN, "example.com")
.mode(FilterMode.ALL)
.build();
CreateWebhookRequest request = CreateWebhookRequest.builder()
.url("https://your-app.com/webhook/emails")
.events(WebhookEventType.EMAIL_RECEIVED, WebhookEventType.EMAIL_STORED)
.description("Production email webhook")
.filter(filter)
.build();
WebhookData webhook = inbox.createWebhook(request);
System.out.println("Webhook created: " + webhook.getId());
System.out.println("Secret: " + webhook.getSecret());
// Test the webhook
TestWebhookResponse testResult = inbox.testWebhook(webhook.getId());
if (testResult.isSuccess()) {
System.out.println("Webhook test successful!");
} else {
System.err.println("Webhook test failed: " + testResult.getError());
}
// List all webhooks
List<WebhookData> webhooks = inbox.listWebhooks();
System.out.println("Total webhooks: " + webhooks.size());
// Update webhook
UpdateWebhookRequest update = UpdateWebhookRequest.builder()
.description("Updated description")
.build();
inbox.updateWebhook(webhook.getId(), update);
// Rotate secret periodically
// RotateSecretResponse newSecret = inbox.rotateWebhookSecret(webhook.getId());
// Cleanup
// inbox.deleteWebhook(webhook.getId());
// inbox.delete();
}
}
}
FeatureWebhooksSSEPolling
DeliveryPush to your serverPush to clientPull from client
ConnectionNone requiredPersistentRepeated requests
LatencyNear real-timeReal-timeDepends on interval
Server requiredYes (webhook endpoint)NoNo
Firewall friendlyYesUsuallyYes
Best forServer-to-serverBrowser/client appsSimple integrations