Skip to content
API DocsDocs

Handling The Callback

Verify and process APM webhook callbacks

3 min readUpdated Aug 17, 2026

After the customer completes (or fails) the payment, Exirom sends a server-to-server POST to your callbackUrl with the final transaction status.

#Processing Steps

  1. Verify the X-Checksum header — recompute the unchanged APM-payment HMAC-SHA256 using accountId | orderAmount | orderCurrency | transactionId and compare. Reject if it doesn't match. See Checksum Authentication.
  2. Look up the transaction by requestId or transactionId
  3. Update your system based on transactionStatus
  4. Return 200 OK promptly — Exirom retries on non-2xx responses

Checksum amount: Use the exact orderAmount representation received in the callback when verifying its checksum — do not reuse the original request amount.

#Havale Pay-ins: Credit the Processed Amount

This rule applies only when your approved pay-in route uses Havale, including Havale routed through SMART_BANK_TRANSFER. Before enabling Havale through Smart Bank Transfer or a direct API integration, confirm availability and integration requirements with your Account Manager.

When the callback has transactionStatus: "SUCCEED", credit the customer with processedAmount in processedCurrency. Never credit the requested amount or callback orderAmount when they differ from the processed values.

For example, this request asks the customer for 85,000 TRY:

{
  "amount": "85000.00",
  "currency": "TRY",
  "requestId": "merchant-request-123",
  "apmPayload": {
    "paymentMethod": "SMART_BANK_TRANSFER",
    "paymentType": "BANK_TRANSFER",
    "customFields": {}
  }
}

The complete successful callback body can contain a processed amount of 1,100 TRY. Values are redacted, but the payload shape, field names, and null values match the callback:

{
  "order": {
    "date": null,
    "name": null,
    "title": null,
    "siteId": null,
    "orderId": null,
    "domainName": null
  },
  "accountId": "merchant-account-id",
  "createdAt": "2026-08-17T12:00:00.000000",
  "requestId": "merchant-request-123",
  "callbackUrl": "https://merchant.example.com/payments/callback",
  "declineCode": null,
  "orderAmount": "85000.0",
  "orderCurrency": "TRY",
  "transactionId": "transaction-456",
  "billingDetails": {
    "ssn": null,
    "city": null,
    "email": "customer@example.com",
    "phone": null,
    "state": null,
    "country": "TR",
    "address1": null,
    "lastName": "REDACTED",
    "firstName": "REDACTED",
    "postalCode": null,
    "dateOfBirth": null,
    "identityCode": null,
    "externalUserId": null
  },
  "conversionRate": "1.0",
  "apmResponseData": {
    "bic": null,
    "iban": null,
    "qrData": null,
    "country": null,
    "bankCity": null,
    "bankCode": null,
    "bankName": null,
    "sortCode": null,
    "iframeUrl": null,
    "swiftCode": null,
    "actionType": "REDIRECTION",
    "branchCode": null,
    "qrDeepLink": null,
    "paymentType": "BANK_TRANSFER",
    "redirectUrl": "https://redacted.example/redirect",
    "bankMetadata": null,
    "accountNumber": null,
    "paymentMethod": "SMART_BANK_TRANSFER",
    "routingNumber": null,
    "transitNumber": null,
    "securityAnswer": null,
    "bankAccountType": null,
    "securityQuestion": null,
    "accountHolderName": null,
    "institutionNumber": null,
    "maskedAccountNumber": null,
    "bankCleaningSystemId": null,
    "providerTransactionId": "provider-transaction-456"
  },
  "processedAmount": "1100.0",
  "declineSubReason": null,
  "apmRequestPayload": {
    "bic": null,
    "iban": null,
    "bankCity": null,
    "bankCode": null,
    "bankName": null,
    "sortCode": null,
    "swiftCode": null,
    "branchCode": null,
    "personalId": null,
    "paymentType": "BANK_TRANSFER",
    "customFields": {},
    "accountNumber": null,
    "paymentMethod": "SMART_BANK_TRANSFER",
    "routingNumber": null,
    "transitNumber": null,
    "bankAccountType": null,
    "accountHolderName": null,
    "institutionNumber": null,
    "bankCleaningSystemId": null,
    "accountHolderPhoneNumber": null
  },
  "processedCurrency": "TRY",
  "transactionStatus": "SUCCEED"
}

In this example, credit 1,100 TRY from processedAmount and processedCurrency — not 85,000 TRY from the request or orderAmount.

#Callback Routing

Exirom appends query parameters to your callbackUrl so you can route before reading the body:

  • ?paymentMethod=apm&apmType={METHOD} — APM callbacks
  • ?paymentMethod=card — card callbacks

Use apmType to select the correct parser — apmResponseData structure varies by APM.

For the full callback payload schema, see API Reference: GET /api/v1/payments/apm/info/{id}.

Was this helpful?