Transaction Events

Triggered when a transaction's status changes.

transaction_created:

{
  "event": "transaction_created",
  "data": {
    "transactionId": "BeOfXV1NVIcZlsSVeQAF",
    "status": "pending",
    "type": "withdrew",
    "externalReference": "unique-ref"
  },
  "timestamp": "2024-10-03T16:33:14.600Z"
}

transaction_updated:

{
  "event": "transaction_updated",
  "data": {
    "transactionId": "string",
    "amount": 100,
    "currency": "KES",
    "status": "string",
    "type": "string",
    "externalReference": "string",
    "stepRequired": "string", // optional e.g otp, redirect, ussd.
    "stepMetadata": {}, // optional metadata for the required step
    "redirectUrl": "https//test-redirect.com", // provided when stepRequired is "redirect"
    "method": "string", // optional e.g momo, bank etc
    "thirdPartyReference": "string", // optional. e.g Mpesa reference
    "note": "string", // optional. e.g error message,
    "txId": "string", // optional e.g offramp/onramp transaction hashes
    "originalTransactionId": "string" // optional, only returned on refund transaction updates
  },
  "timestamp": "ISO-8601 timestamp"
}

Amounts and fees

The webhook includes amount and currency. For a standard collection, these represent the amount collected on the source side (senderAmount and senderCurrency). The webhook amount is not adjusted for HoneyCoin fees.

When you need the receiving-side amount or fee details, retrieve the transaction with Get Transaction. For standard collections, receiverAmount is the gross receiving-side amount after any currency conversion, while fees are returned separately as fee.feeAmount and fee.currency.

For a successful collection:

  • If fee.currency and receiverCurrency are the same, the net amount added to your HoneyCoin balance is receiverAmount - fee.feeAmount.
  • If the currencies differ, treat the received amount and fee as separate movements in their respective currencies.

Whether you credit your customer the gross receiverAmount or an amount adjusted for fees depends on your own fee policy.

{
  "event": "transaction_updated",
  "data": {
    "transactionId": "test123",
    "amount": 100,
    "currency": "KES",
    "status": "pending",
    "type": "deposit",
    "externalReference": "mnvmcvcmvlc",
    "stepRequired": "redirect",
    "redirectUrl": "https//test-redirect.com",
    "method": "momo" // optional e.g momo, bank etc
  },
  "timestamp": "ISO-8601 timestamp"
}
{
  "event": "transaction_updated",
  "data": {
    "transactionId": "test123",
    "amount": 100,
    "currency": "KES",
    "status": "pending",
    "type": "deposit",
    "externalReference": "mnvmcvcmvlc",
    "stepRequired": "otp",
    "method": "momo" // optional e.g momo, bank etc
  },
  "timestamp": "ISO-8601 timestamp"
}

USSD Required

A pending mobile money transaction can emit transaction_updated when the provider returns a USSD code. This event requests customer action; it does not confirm payment. The following example shows the relevant fields; <provider-returned-code> is a placeholder.

{
  "event": "transaction_updated",
  "data": {
    "transactionId": "ussd_payment_123",
    "status": "pending",
    "type": "deposit",
    "method": "momo",
    "externalReference": "order_12345",
    "stepRequired": "ussd",
    "stepMetadata": {
      "ussdCode": "<provider-returned-code>",
      "description": "Dial <provider-returned-code> and follow the prompts to complete payment",
      "expiry": 1790854200000
    }
  },
  "timestamp": "2026-10-01T10:00:00Z"
}

Display stepMetadata.ussdCode exactly as returned and show stepMetadata.description as plain text when present. The customer dials the code and completes the provider's prompts on their phone. No OTP validation or USSD confirmation request is required.

stepMetadata.expiry is optional and is a Unix timestamp in milliseconds. If a valid expiry is present, you can show a countdown; if it is missing, omit the countdown. An expired code does not by itself confirm payment failure. Keep listening for the final webhook or use Get Transaction to confirm the outcome before providing value.

See Handle USSD Codes for the complete flow and missing-code handling.

Refund transaction update:

Refunds use the standard transaction_updated event. In the payload, transactionId is the refund transaction ID and originalTransactionId is the original deposit transaction being refunded.

{
  "event": "transaction_updated",
  "data": {
    "transactionId": "unique-refund-transaction-id",
    "originalTransactionId": "lBK9bMny2gs4hLsG3XGq",
    "status": "successful",
    "type": "refund",
    "externalReference": "refund_order_12345",
    "amount": 25,
    "currency": "KES",
    "method": "momo"
  },
  "timestamp": "ISO-8601 timestamp"
}

Failed off-ramp with automatic refund:

Automatic off-ramp refunds use the original type: "offramp" transaction. The failed webhook confirms that the fiat payout failed and may arrive before the on-chain refund is submitted:

{
  "event": "transaction_updated",
  "data": {
    "transactionId": "zQQvoWnhYED3yXYEo7y7",
    "status": "failed",
    "type": "offramp",
    "externalReference": "offramp_user_001",
    "method": "momo",
    "depositAddress": "0x318aa0c46120bb214f7da0d53183d0b1c9be7047",
    "txId": "0xc3acfde7f212fcb902045119ee41299249ca153fc5c2730b0e8adba749846b53",
    "note": "Payout failed."
  },
  "timestamp": "ISO-8601 timestamp"
}

status: "failed" is not refund confirmation. Honeycoin does not send a separate refund webhook when the original off-ramp's refundTransactionHash is added. Query GET /api/b2b/transactions/{transactionId} and treat the refund as confirmed only when refundTransactionHash is present.

If the failed off-ramp has no deposit txId, Honeycoin did not detect crypto for that transaction and no automatic refund is created.

Status Values:

  1. pending: Transaction is being processed.
  2. successful: Transaction successfully completed.
  3. failed: Transaction failed.