Skip to main content
Skip to main content

Webhooks

Webhooks are notifications that are automatically sent when specific predefined events occur.

Webhooks are commonly used, for instance, when payment processors need to notify a client or end user that a charge succeeded or when a subscription renews.

They replace polling. This polling might have been:

  • Software polling for a status change either through a loop, or timed queries.
  • Manually by a user explicitly polling expecting a response or pending status to be returned.

Each merchant defines their own set of webhooks. A merchant can only affect their set webhooks. They cannot view or modify other merchant's webhooks. Likewise, a partner can only affect merchants associated to them. They cannot affect merchants of other partners.

Required API Permissions

Required API permission: Webhooks

If the permission for webhooks needs to be granted, contact your integration support team.

Webhook Workflows

To create and manage Aurora webhooks, use the following procedures and workflows.

Creating Webhooks

1) Determine which event types are of interest. To list these, use GET /v2/webhooks/event-types

An event type is an identifier describing a specific event. For example, the event may be when a payment is approved, a payment fails, or an API key is created. The list of available events is presented by Aurora and only those events can be selected from. Event types may be periodically added.

Webhooks can accept one or more event types. Each event type is evaluated independently. Selected events are not required to be related and may relate to different transaction types or workflows. For organizational reasons, multiple webhooks can be created. Each webhook can have its own set of event types.

The following is the list of available event types.

Event TypeDescriptionCategoryEvent Id
payment.ach.scheduledACH transaction created and scheduledACH Payments9
payment.ach.in_progressACH transaction submitted to networkACH Payments10
payment.ach.heldACH transaction placed on hold for reviewACH Payments11
payment.ach.releasedACH transaction released from holdACH Payments12
payment.ach.cancelledACH transaction cancelled before processingACH Payments13
payment.ach.clearedACH transaction successfully clearedACH Payments14
payment.ach.charged_backACH transaction returned or charged backACH Payments15
payment.ach.failedACH transaction failed due to processing errorACH Payments16
payment.ach.refundedACH transaction refunded to originatorACH Payments17
api_key.createdNew API key createdAPI Keys31
api_key.deletedAPI key revoked or deletedAPI Keys32
payment.card.authorizedCard payment authorization approvedCard Payments3
payment.card.capturedAuthorized card payment capturedCard Payments4
payment.card.declinedCard payment declined by issuerCard Payments5
payment.card.failedCard payment failed due to processing errorCard Payments6
payment.card.voidedCard authorization voided before captureCard Payments7
payment.card.refundedCard payment refunded to cardholderCard Payments8
invoice.createdNew invoice createdInvoices18
invoice.paidInvoice marked as paidInvoices19
merchant.createdNew merchant account createdMerchants30
quick_payment.createdQuick payment link createdQuick Payments25
quick_payment.paidQuick payment link paidQuick Payments26
settlement.batch.completedBatch settlement has been processed and settledSettlement2
subscription.createdNew subscription createdSubscriptions21
subscription.paidSubscription payment successfully collectedSubscriptions22
subscription.payment_failedSubscription payment attempt failedSubscriptions23
subscription.delinquentSubscription entered delinquent state after repeated failuresSubscriptions24
terminal.addedNew terminal registered to accountTerminals33
terminal.out_of_paperTerminal paper roll is emptyTerminals35

2) Create the webhook
To create the webhook, use POST /v2/webhooks/endpoints
Three parameters must be included.

  • name. This is the name of the webhook. It is a friendly, free-formed name that is convenient to recognize or indicate it's purpose.
  • endpointUrl. This is an HTTPS URL or IP address of the server accepting the webhook. The URL is provided by the client.
  • eventTypes. This is the set, an array of strings, of all events the webhook will respond to. See the event types table above.

3) Test the webhook
This step is optional but recommended. This verifies the webhook's endpoint (endpointUrl) is reachable by sending a ping.
To test the webhook, use POST /v2/webhooks/endpoints/{webhookId}/ping.

Webhook Success or Failure

A successful webhook delivery is when the client’s server (the endpointUrl) received, accepted, and acknowledged an incoming webhook. This is a valid confirmation (a 200 series response within the timeout limit) from the client’s endpoint.

The webhook may not be successfully delivered. Reasons include a response other than a 200 series response, timed out, DNS failure, or the connection was refused. In that case, it will be re-sent automatically over a period of time.

Failed deliveries are retried with exponential backoff:

AttemptDelay After FailureCumulative Time
1 (initial)0
21 minute~1 min
35 minutes~6 min
430 minutes~36 min
52 hours~2.5 hours
68 hours~10.5 hours
724 hours~34.5 hours

The same values are returned with GET {{baseURL}}/v2/webhooks/delivery-logs/{{webhookId}}

  • nextRetryAt. This indicates the date-time of the next try.
  • attemptNumber. This indicates the number of the upcoming attempt.

If the webhook could never be successfully delivered, it is:

  • Cancelled. No further attempts will be made.
  • Marked as failed. This will be entered in the delivery log files.

An unsuccessful webhook delivery may be manually retried.
To retry a failed delivery, use POST /v2/webhooks/delivery-logs/{webhookId}/retry

Managing Webhooks

The following set of endpoints manage webhooks.

  • This endpoint provides a list of all available webhook for the merchant.
    To list all the available webhooks, use GET /v2/webhooks/endpoints
  • This endpoint retrieves webhook details specified by its webhookId.
    To retrieve a specific webhook, use GET /v2/webhooks/endpoints/{webhookId}
  • This endpoint provides an ability to update or change a webhook. For example, the set of event types may be changed without having to create a new webhook.
    To modify or update a specific webhook, use PUT /v2/webhooks/endpoints/{webhookId}
  • This endpoint deletes the specific webhook. This action can neither be undone nor can the webhook be retrieved. This does not erase entries in the log file.
    To delete a specific webhook, use DELETE /v2/webhooks/endpoints/{webhookId}

Monitoring Webhooks

The following set of endpoints monitor webhooks. These may be used to verify success of an attempt or assist in determining the cause of a problem.

  • This endpoint lists success level of delivery attempts.
    To view webhooks delivery attempts log file, use GET /v2/webhooks/delivery-logs
  • This endpoint lists success level of delivery attempts for a specified webhook.
    To view a specified webhook delivery attempts log file, use GET /v2/webhooks/delivery-logs/{webhookId}
  • This endpoint exports the delivery logs file. It may be specified as either a CSV or JSON format.
    To export webhooks delivery attempts log file, use GET /v2/webhooks/delivery-logs/export
  • This endpoint retries a failed delivery.
    To retry a failed delivery, use POST /v2/webhooks/delivery-logs/{id}/retry

Client Handling of Messages

The webhook sends a message to the URL specified by the client. This is endpointUrl of POST {{baseURL}}/v2/webhooks/endpoints. The message is considered successfully sent after a send confirmation is received. If that send confirmation was not successfully confirmed, the message will be sent, or retried, a number of times before failing.

After being successfully sent to the receiving endpoint or URL, it is the client's responsibility to handle it after that. As a best practice, we recommend that the message is first verified to be from Aurora. This step greatly increases security and reduces system vulnerabilities such as spoofing, tampering, or data injection. The IP address could be blocked to prevent future intrusions.

HMAC Verification

To use HMAC verification:

  1. Retrieve your signing secret from your secure environment variables.
  2. Capture the raw request body as a string before any JSON parsing occurs.
  3. Extract the signature value from the webhook-signature header (the part after the v1,). Also extract headers webhook-id and webhook-timestamp. These will be used on the next step.
  4. Generate an HMAC-SHA256 hash using the secret as the key and the raw body as the message. Content to hash: {eventId}.{eventTimestamp}.{rawBody}
  5. Encode the generated hash to Base64 to match the header format.
  6. Perform a constant-time comparison between your generated signature and the one from the header.

The following code examples illustrate HMAC verification.

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.List;
import java.util.Map;

public class WebhookExample {

// In production, load this from an environment variable or a secrets manager.
// Never hardcode secrets in source code. Example: System.getenv("WEBHOOK_SECRET")
private static final String WEBHOOK_SECRET = "your_webhook_secret_here";
private static final int PORT = 3000;

public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
server.createContext("/webhook-listener", WebhookExample::handleRequest);
server.start();
System.out.println("Webhook listener running on port " + PORT);
}

private static void handleRequest(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
sendResponse(exchange, 404, "Not Found");
return;
}

// Read the raw body before any parsing.
byte[] rawBodyBytes = exchange.getRequestBody().readAllBytes();
String rawBody = new String(rawBodyBytes, StandardCharsets.UTF_8);

if (!verifySignature(exchange, rawBody)) {
sendResponse(exchange, 401, "Unauthorized");
return;
}

// Acknowledge receipt before processing so the payment processor doesn't time out.
sendResponse(exchange, 200, "OK");

// Parse and handle the event after responding.
handleEvent(rawBody);
}

private static boolean verifySignature(HttpExchange exchange, String rawBody) {
Map<String, List<String>> headers = exchange.getRequestHeaders();
String header = getHeader(headers, "webhook-signature");
String eventId = getHeader(headers, "webhook-id");
String eventTimestamp = getHeader(headers, "webhook-timestamp");

if (header == null || eventId == null || eventTimestamp == null) return false;

String[] parts = header.split(",", 2);
if (parts.length < 2) return false;
String receivedSignature = parts[1];

try {
// The signed payload is the event ID, timestamp, and raw body concatenated with dots.
String signedPayload = eventId + "." + eventTimestamp + "." + rawBody;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(WEBHOOK_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String computedSignature = Base64.getEncoder().encodeToString(
mac.doFinal(signedPayload.getBytes(StandardCharsets.UTF_8))
);
return MessageDigest.isEqual(
receivedSignature.getBytes(StandardCharsets.UTF_8),
computedSignature.getBytes(StandardCharsets.UTF_8)
);
} catch (Exception e) {
return false;
}
}

// Called after the webhook signature has been verified. Add your business logic here
// to handle each event type and trigger the appropriate actions in your application.
private static void handleEvent(String rawBody) {
// TODO: parse rawBody into your preferred JSON model and handle by event type.
// Example: if ("transaction.card.captured".equals(event.getType())) { ... }
System.out.println("Received event: " + rawBody);
}

private static String getHeader(Map<String, List<String>> headers, String name) {
List<String> values = headers.get(name);
return (values != null && !values.isEmpty()) ? values.get(0) : null;
}

private static void sendResponse(HttpExchange exchange, int status, String body) throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
}

Aftermath

Once the sender is verified, the client can process the message as they need.