Webhooks

If you integrate directly with April, you are able to use webhooks to be notified when an event happens on your account. Webhooks are particularly useful for asynchronous events such as a payout being paid to a merchant's bank account, a sub-merchant completing onboarding or a transaction being paid.

How to receive webhooks

Webhook endpoints can be registered via the Create Webhook Subscription API call. Subscriptions can be made at either the marketplace or merchant level. Each subscription registers a fully qualified HTTPS endpoint that will accept POST calls over the public internet.

Existing subscriptions can be suspended or activated via Update Webhook Subscription or queried using Query Webhook Subscriptions

Handling incoming webhooks

Webhook events are delivered to your registered HTTPS endpoint as a POST request with the event details provided in JSON format in the request body.

Event payload

The overall structure of the event messages will be the same regardless of the type of events subscribed.

Copy
Copied
{
  "marketplaceId":"mktp_ZJOpaegtuDrIXjgm",
  "metadata":{
    "service":"dispatchment"
  },
  "webhookSubscriptionId":"webhook_ZJOpaugtuDrIXjgs",
  "webhookEventType":"TransactionPaid",
  "requestId": "a0a3e2b6-99bd-4a7d-ae30-d3f7340c59e5",
  "event":{ ... },
  "timestamp":"2023-06-22T01:52:52.096861Z"
}

The top level JSON object in the request body will contain the following fields:

  • marketplaceId or merchantId - if the subscription is at the marketplace level, the matching marketplaceId will be provided, otherwise the merchantId will be provided.
  • metadata - this is an optional JSON object that you can provide on the Create Webhook Subscription request that you can use to provide some context to this subscription.
  • webhookSubscriptionId - unique system identifier for the webhook subscription this event triggered for
  • webhookEventType - the category of event included in this request
  • requestId - the unique identifier for the API call that triggered the generation of this event. This corresponds to the Request-Id response header on the API call itself.
  • event - Details of the event. The structure of the event is specific to the category of the event as details below.
  • timestamp - When the webhook event was generated in ISO-8601 format.

Acknowledging receipt and retries

April marks an event as successfully delivered if your service returns a 2xx status code. If your service is not reachable or returns a different status code, then April will retry up to 100 times, doubling the interval between each retry up to a maximum interval of one hour.

Verifying the sender

Since webhooks require you to expose a public endpoint, it's possible for third parties to impersonate April requests. Every legitimate webhook request from April includes a payload-signature request header.

Copy
Copied
payload-signature: FfB76MV8JQGiCpyttEa8k2u8kYQy1k/B5WatzlgWf9w=

This is a cryptographic signature of the event payload generated using the secret provided in the response when creating the corresponding webhook subscription.

The following code examples show you how to reproduce the signature yourself from the event to confirm it was sent by April. If the payload-signature header is missing, or the signature you generate doesn't match the provided one, the event is not a genuine one.

In each of the following examples the body variable contains the String based content of the request.

javascriptjavapython
Copy
Copied
const crypto = require('crypto');

function generateSignature(body, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(body);
  return hmac.digest('base64');
}
Copy
Copied
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public String generateSignature(String body, String secret) {
    var bytes = body.getBytes(StandardCharsets.UTF_8);
    String algorithm = "HmacSHA256";
    try {
        Mac mac = Mac.getInstance(algorithm);
        SecretKeySpec key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), algorithm);
        mac.init(key);
        byte[] hash = mac.doFinal(bytes);

        return Base64.getEncoder().encodeToString(hash);
    } catch (Exception ex) {
    throw new RuntimeException(ex);
    }
}
Copy
Copied
import hmac
import hashlib
import base64

def generate_signature(body, secret):
    hmac_hash = hmac.new(secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256)
    return base64.b64encode(hmac_hash.digest()).decode('utf-8')

Supported webhook event types

A subscription is for one or more of the following event types. For a given subscription, all events matching the subscribed event types will be sent to the registered URL.

Merchant events

  • MerchantCreated - A sub-merchant of a marketplace has been created and has started the onboarding process.
  • MerchantActivated - A merchant has been fully onboarded and is activated for accepting payments.
  • MerchantSuspended - A merchant has been suspended, disabling creation of new payments.
  • MerchantAvailableBalanceUpdated - The available balance for a merchant has been updated.
  • MerchantBankAccountAdded - A new bank account has been added to a merchant account.
  • MerchantPrimaryBankAccountUpdated - The bank account used for all automated payouts and the default for manual payouts has been updated.
Copy
Copied
{
  "MerchantEvent": {
    "marketplaceId": "mktp_ZzLK_FocQiGqag1d",
    "createdAt": "2024-11-03T18:00:00.040Z",
    "merchantId": "mcht_ZzLLBVocQiGqag2V",
    "tradingCurrency": "AUD",
    "businessName": "Los Pollos Hermanos",
    "bankAccounts": [
      {
        "accountId": "xacc_ZzLLBVocQiGqag2i",
        "accountName": "Los Pollos Hermanos",
        "referenceAccountId": "NBO Float",
        "bankName": "NBO",
        "currency": "AUD",
        "country": "AU",
        "bsb": "110000",
        "accountNumber": "000123456",
        "createdAt": "2024-11-12T03:27:20.771Z",
        "isPrimaryForCurrency": true
      }
    ],
    "merchantType": "ServiceProvider",
    "referenceMerchantId": "hermanos",
    "status": "Onboarding",
    "availableBalance": 0,
    "updatedAt": "2024-11-03T18:00:00.040Z"
  }
}

Pay Plan events

  • PayPlanCreated - The pay plan object has been created, but has not been successfully associated with an order yet.
  • PayPlanActivated - The pay plan has successfully been established, the initial payment, if any, has been received and one or more future instalments are yet to be paid.
  • PayPlanCompleted - The payment plan has been completely paid, including any fees/penalties.
  • PayPlanInDefault - The plan is overdue and has not been paid after several soft collection attempts.
Copy
Copied
{
  "PayPlanEvent": {
    "outstandingAmount": 120000,
    "orderId": "ordr_ZJK0jegtuDrIXjfB",
    "payPlanVariant": "payplan_30_days_0_100",
    "transactionId": null,
    "marketplaceId": "mktp_ZJK0hegtuDrIXjeS",
    "createdAt": "2023-06-21T08:27:56.458Z",
    "referenceOrderId": "ORD23489ff2a",
    "merchantId": "mcht_ZJK0hugtuDrIXjeY",
    "initialAmount": 40000,
    "customerId": "cust_ZJK0iugtuDrIXje3",
    "currency": "AUD",
    "payPlanId": "plan_ZJK0jOgtuDrIXje9",
    "state": "Active",
    "planAmount": 120000,
    "updatedAt": "2023-06-21T08:27:57.456Z"
  }
}

Full Payment events

  • PayInFullCreated - A payment token representing a full payment has been created. No money transfer has occurred at this point.
  • PayInFullAccepted - A pay in full payment has been accepted by the customer but the payment method is asynchronous (e.g. direct debit or bank transfer).
  • PayInFullAuthorised - A card based pay in full payment has authorised a payment (reserving the funds), but it has not been captured yet.
  • PayInFullCompleted - A pay in full payment has been successfully paid.
Copy
Copied
{
  "PayInFullEvent": {
    "orderType": "paybylink",
    "orderMetadata": null,
    "amount": 111650,
    "orderId": "ordr_YwRMKLamGhOoW0Ym",
    "transactionId": "tran_ZJq00QJX-CjIe7_x",
    "marketplaceId": "mktp_Yj0NLDzhf1ITHYWc",
    "createdAt": "2022-08-23T03:40:24.669Z",
    "referenceOrderId": "Pipe order",
    "merchantId": "mcht_YSxT5hTjzVNgADKk",
    "customerId": null,
    "currency": "AUD",
    "state": "Completed",
    "updatedAt": "2023-06-27T10:07:17.821Z"
  }
}

Transaction events

  • TransactionPaid - An order has been completed from the merchants perspective. For pay in full payments, the payment has been successfully completed. For pay plans, the plan has been successfully activated.
  • TransactionFailed - An attempt to pay for an order has failed.
Copy
Copied
{
  "TransactionEvent": {
    "amount": 120000,
    "currency": "AUD",
    "transactionId": "tran_ZJKjYegtuDrIXjeB",
    "transactionStatus": "paid",
    "marketplaceId": "mktp_ZJKjVegtuDrIXjdQ",
    "merchantId": "mcht_ZJKjV-gtuDrIXjdW",
    "referenceMerchantId": "chickenbros-11a",
    "customerId": "cust_ZJKjXOgtuDrIXjd1",
    "referenceCustomerId": "strata-07a08d93-f7d8-4f40-8294-fac9c96c257f",
    "orderId": "ordr_ZJKjYegtuDrIXjd_",
    "referenceOrderId": "referenceOrderId-5c80e9fa-f869-4c73-9ede-d85de0cb9669",
    "orderType": "online",
    "orderMetadata": {
      "invoiceId": "AB44111"
    },
    "payType": "PayInFull",
    "availableOn": "2024-11-06",
    "errorCode": null,
    "errorMessage": null,
    "errorDetails": null,
    "createdAt": "2023-06-21T07:14:41.543Z",
    "updatedAt": "2023-06-21T07:14:42.205Z"
  }
}

and for a failure:

Copy
Copied
{
  "amount": 185000,
  "currency": "AUD",
  "transactionId": "tran_Z1fMf3sBPzcg4sZa",
  "transactionStatus": "failed",
  "marketplaceId": "mktp_Z1fMYbmcay9nWIib",
  "merchantId": "mcht_Z1fMYnsBPzcg4sYk",
  "referenceMerchantId": "autotest-18bf4a50-415f-40b0-a1d3-f5b20f8be2bc",
  "customerId": "cust_Z1fMcHsBPzcg4sZP",
  "referenceCustomerId": "cust-ref-34d81f0c-48eb-4188-9d82-7472ced11909",
  "orderId": "ordr_Z1fMf7mcay9nWIjP",
  "referenceOrderId": "referenceOrderId-b0cc126f-3d2a-45c2-952c-cafd0c941929",
  "orderType": "paybylink",
  "orderMetadata": {
    "stats": {
      "tradeCount": 622,
      "rating": "premo"
    },
    "traderRating": 5,
    "coffee": "strong"
  },
  "payType": "PayInFull",
  "availableOn": "2024-11-06",
  "errorCode": "insufficient_funds",
  "errorMessage": "Insufficient funds",
  "errorDetails": "ISO8583 Error 51 Insufficient funds/over credit limit",
  "createdAt": "2024-11-03T18:00:00.050Z",
  "updatedAt": "2024-11-03T18:00:03.031Z"
}

Dispute events

  • DisputeCreated - A customer has raised a dispute for a payment with their financial provider.
  • DisputeUpdated - An update has been received on an existing dispute.
Copy
Copied
{
  "DisputeEvent": {
    "disputeId": "dispute_ZJK0jOgtuDrIXje9",
    "disputeAmount": 45131,
    "disputeFee": 1500,
    "transactionId": "tran_ZJKjYegtuDrIXjeB",
    "orderId": "ordr_ZJKjYegtuDrIXjd_",
    "reason": "fraudulent",
    "state": "needs_response",
    "outcome": "pending",
    "payType": "PayPlan",
    "marketplaceId": "mktp_ZJKjVegtuDrIXjdQ",
    "merchantId": "mcht_ZJKjV-gtuDrIXjdW",
    "customerId": "cust_ZJKjXOgtuDrIXjd1",
    "createdAt": "2023-06-21T07:14:42.205Z",
    "updatedAt": "2023-06-27T10:07:17.821Z"
  }
}

Notable fields in the event

  • disputeAmount - The disputed amount in minor currency units
  • disputeFee - The dispute fee charged to the merchant in minor currency units
  • reason - The reason provided by the customer for raising the dispute
  • state - The internal state of the dispute
  • outcome - The overall state of the dispute. Will be one of pending , lost or won .

Payout events

  • PayoutFailed - We were unable to make a payout to a merchant's primary bank account.
  • PayoutPaid - A payout was successfully delivered to a merchant's primary bank account.
Copy
Copied
{
  "PayoutEvent": {
    "payoutId": "payout_ZJK0jOgtuDrgXje3",
    "payoutAmount": 981458,
    "currency": "AUD",
    "openingBalance": 0,
    "closingBalance": 0,
    "arrivalDate": "2023-06-27T22:15:43.000Z",
    "settlementDate": "2023-06-28",
    "status": "paid",
    "failureReason": null,
    "statementDescriptor": "Manual payout P-15343",
    "marketplaceId": "mktp_ZJKjVegtuDrIXjdQ",
    "merchantId": "mcht_ZJKjV-gtuDrIXjdW",
    "customerId": "cust_ZJKjXOgtuDrIXjd1",
    "createdAt": "2023-06-21T07:14:42.205Z",
    "updatedAt": "2023-06-27T10:07:17.821Z"
  }
}

Notable fields in the event:

  • payoutAmount - The amount paid out to the merchant in minor currency units.
  • arrivalDate - When the payout is initiated on the April side. Typically, the funds are cleared within a few hours of this time.
  • settlementDate - The date the payout will be attempted to settle with the merchant.
  • status - One of pending (payout not yet initiated), in_transit (on its way to the merchants bank account), paid (cleared successfully), failed (the payout failed to clear - typically due to incorrect bank account details), reversed (where a failed payout is credited to the next payout).
note

Most of the time openingBalance and closingBalance are both 0.

For payouts where the total from negative entries such as refunds is greater than the total from positive entries such as payments, then the closing balance will be a negative amount and no deposit to the merchant's bank account will be made.

The closing balance from that payout is then carried forward as the opening balance of the next payout and is included with the new entries when calculating the payout amount.

Invoice events

  • InvoiceIssued - An invoice was issued to a gross settlement merchant for their outstanding platform fees.
  • InvoiceDue - An outstanding invoice is due for payment.
  • InvoicePaymentFailed - A payment made towards an invoice has failed.
  • InvoicePaid - An invoice was successfully paid.
Copy
Copied
{
  "InvoiceEvent": {
    "invoiceId": "minv_ZoGnzJ5HrRbx7zNu",
    "invoiceReference": "fG2RS64BJIrezf3O",
    "invoiceAmount": 55169,
    "currency": "AUD",
    "paidAmount": 55169,
    "invoiceStartDate": "2024-05-01",
    "invoiceEndDate": "2024-05-31",
    "issuedOn": "2024-06-01",
    "dueDate": "2024-06-08",
    "status": "paid",
    "summary": "Fees invoice minv_ZoGnzJ5HrRbx7zNu for the period 01 May 2024 to 31 May 2024 including 58 pay in full fees (-$551.69 AUD). With new entries totalling -$551.69 AUD, the invoice is for $551.69 AUD",
    "failureReason": null,
    "marketplaceId": "mktp_ZJKjVegtuDrIXjdQ",
    "merchantId": "mcht_ZJKjV-gtuDrIXjdW",
    "createdAt": "2024-06-01T07:14:42.205Z",
    "updatedAt": "2024-06-01T10:07:17.821Z"
  }
}

Notable fields in the event:

  • invoiceReference - Unique reference for this invoice that can be included on manually generated payments.
  • summary - Summary of the contents of the invoice.
  • status - One of outstanding (invoice issued but not paid), overdue (invoice has not been paid yet and the due date has past), accepted (an asynchronous payment method like direct debit has been initiated but not yet completed), paid (cleared successfully), cancelled (invoice has been cancelled).

Payment Source events

  • PaymentSourceCreated - A payment source has been successfully created.
  • PaymentSourceMethodUpdated - A payment source has been successfully updated.
  • PaymentSourceDeleted - A payment source has been deleted.
Copy
Copied
{
  "PaymentSourceEvent": {
    "paymentSourceId": "psrc_ZzLmFddf40w3FsVA",
    "customerId": "cust_ZtqDr4zWf1aYVSNS",
    "merchantId": "mcht_ZJLKZugtuDrIXjfs",
    "customerDelegateId": "custd_Z1K4s5u-UlthyY-M",
    "delegateEmailAddress": "hank.schrader@aol.com",
    "marketplaceId": "mktp_ZJLKZegtuDrIXjfk",
    "sourceMethod": {
      "CardMethod": {
        "country": "AU",
        "last4": "1234",
        "brand": "MasterCard",
        "funding": "credit",
        "cardBin": "456912",
        "expiryMonth": 12,
        "expiryYear": 2026
      }
    },
    "usageScope": "ScopeDelegate",
    "createdAt": "2024-09-21T10:01:09.931Z",
    "updatedAt": "2024-09-21T10:01:09.931Z"
  }
}
Copyright © April Solutions 2023. All right reserved.