> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://ixopay.ferndocs.com/developer-hub/documentation/guides/getting-started/callbacks/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://ixopay.ferndocs.com/_mcp/server. # Callbacks The IXOPAY platform provides a callback mechanism that allows you to receive real-time updates on the status of your payment transactions. This is useful for keeping your own system in sync with the status of your payments, and for triggering other actions based on the status of a payment (e.g. sending a confirmation email to your customer). ```mermaid %%{ init: { "sequence": {"mirrorActors": false} } }%% sequenceDiagram participant M as Merchant backend participant G as IXOPAY platform participant PSP activate M M->>G: Perform transaction: setting a callback URL activate G G-->>+PSP: Perform payment PSP-->>-G: Payment complete par G->>+M: Callback M->>M: Update internal state M-->>-G: Callback OK and G-->>M: Payment complete end deactivate M deactivate G loop State changes PSP-->>G: Payment state change activate G G-->>PSP: OK G->>+M: Callback M->>M: Update internal state M-->>-G: Callback OK deactivate G end ``` > **Reference** > > For more details on callbacks, check out the in-depth article on [callbacks](https://documentation.ixopay.com/docs/reference/integration/callbacks "Callbacks reference article") in the reference. To use callbacks, follow these simple steps: ## Step 1: Setting a callback URL When you initiate a payment transaction using one of the transaction methods (debit, preauthorize etc.), you can provide a `callbackUrl` parameter in your request payload. This parameter specifies the URL of your system's endpoint that will receive the callback notifications. > **Callback URL requirements** > > For detailed information on the restrictions that apply to the `callbackUrl` field, please refer to the comprehensive article on [Response handling](https://documentation.ixopay.com/docs/reference/integration/callbacks/response-handling) in the reference section. Here is an example request to create a debit transaction with a callback URL: * curl * Python * PHP * Java ```sh curl --request POST -sL \ --url "https://gateway.ixopay.com/api/v3/transaction/${API_KEY}/debit" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --header "Authorization: Basic $(echo -n "$USERNAME:$PASSWORD" | base64)" \ --data-raw '{ "merchantTransactionId": "your-unique-identifier", "description": "Purchase description shown on credit card statement.", "amount": "9.99", "currency": "EUR", "successUrl": "https://shop.example.org/checkout/success", "cancelUrl": "https://shop.example.org/checkout/cancelled", "errorUrl": "https://shop.example.org/checkout/error", "callbackUrl": "https://api.example.org/callback" }' ``` ```python import requests import json import base64 import os url = "https://gateway.ixopay.com/api/v3/transaction/{apiKey}/debit".format( apiKey=os.environ["API_KEY"] ) auth = base64.b64encode("%s:%s" % (os.environ["USERNAME"], os.environ["PASSWORD"])) payload = json.dumps( { "merchantTransactionId": "your-unique-identifier", "description": "Purchase description shown on credit card statement.", "amount": "9.99", "currency": "EUR", "successUrl": "https://shop.example.org/checkout/success", "cancelUrl": "https://shop.example.org/checkout/cancelled", "errorUrl": "https://shop.example.org/checkout/error", "callbackUrl": "https://api.example.org/callback", } ) headers = { "Content-Type": "application/json", "Accept": "application/json", "Authorization": "Basic {auth}".format(auth=auth), } response = requests.request("POST", url, headers=headers, data=payload) ``` ```php true, CURLOPT_ENCODING => '', CURLOPT_POSTFIELDS =>'{ "merchantTransactionId": "your-unique-identifier", "description": "Purchase description shown on credit card statement.", "amount": "9.99", "currency": "EUR", "successUrl": "https://shop.example.org/checkout/success", "cancelUrl": "https://shop.example.org/checkout/cancelled", "errorUrl": "https://shop.example.org/checkout/error", "callbackUrl": "https://api.example.org/callback" }', CURLOPT_HTTPHEADER => array( 'Content-Type: application/json', 'Accept: application/json', "Authorization: Basic $auth" ), )); $response = curl_exec($curl); curl_close($curl); ``` ```java OkHttpClient client = new OkHttpClient().newBuilder().build(); RequestBody body = RequestBody.create( MediaType.parse("application/json"), "{" + "\"merchantTransactionId\": \"your-unique-identifier\"," + "\"description\": \"Purchase description shown on credit card statement.\"," + "\"amount\": \"9.99\"," + "\"currency\": \"EUR\"," + "\"successUrl\": \"https://shop.example.org/checkout/success\"," + "\"cancelUrl\": \"https://shop.example.org/checkout/cancelled\"," + "\"errorUrl\": \"https://shop.example.org/checkout/error\"," + "\"callbackUrl\": \"https://api.example.org/callback\"" + "}" ); String auth = Base64.getEncoder().encodeToString( "%s:%s".format(System.getenv("USERNAME"), System.getenv("PASSWORD"))); Request request = new Request.Builder() .url("https://gateway.ixopay.com/api/v3/transaction/%s/debit" .format(System.getenv("API_KEY"))) .method("POST", body) .addHeader("Content-Type", "application/json") .addHeader("Accept", "application/json") .addHeader("Authorization", "Basic %s".format(auth)) .build(); Response response = client.newCall(request).execute(); ``` ## Step 2: Setting up a callback endpoint To receive callback notifications from IXOPAY platform, you need to set up an endpoint in your system that can receive and process incoming POST requests. The callback data is sent as a JSON payload in the body of the POST request. The payload contains information about the transaction, including its current state and any relevant details. > **HTTP response** > > Once the callback has been handled by your system, IXOPAY platform expects a HTTP response with status code 200 and a response body of exactly `OK` (trimmed). Any other response — a non-200 status code **or** a body that is not `OK` — is treated as a failed postback and will cause IXOPAY platform to retry sending the callback using an exponential backoff algorithm — see [Reference: Callbacks - Response handling](https://documentation.ixopay.com/docs/reference/integration/callbacks/response-handling#acknowledging-callback-receipt). Here is an example of how you can set up a callback endpoint in your store's backend system: * HTTP * Python: Flask * PHP: Laravel * Java: Spring Boot ```http POST /callback Host: api.example.org Content-Type: application/json Date: Thu, 23 Mar 2023 10:34:47 GMT X-Signature: vbWnLPF+bxvv7c6PId/FXWGlV8HqrtzaC8uqJDbNQBLH1I6V9yF8ePQIsEFsfTJXvQGO1B7hzkPIrwF7J47QVw== { "result": "OK", "uuid": "d94c0d72f3a36e21f16e", "merchantTransactionId": "auto-d94c0d72f3a36e21f16e", "purchaseId": "20260922-d94c0d72f3a36e21f16e", "transactionType": "DEBIT", "paymentMethod": "Creditcard", "amount": "9.99", "currency": "EUR", "returnData": { "cardData": { "type": "visa", "cardHolder": "Alex Smith", "expiryMonth": 9, "expiryYear": 2031, "binDigits": "41111111", "firstSixDigits": "411111", "lastFourDigits": "1111", "fingerprint": "46f7adfeb0a123fb8fcbfasdf6171asd6b3dfas44834c", "threeDSecure": "OFF", "binBrand": "US", "binBank": "Global Trust Bank", "binCountry": "US" } } } ``` ```http HTTP/1.1 200 OK Content-Type: text/plain OK ``` ```python from flask import Flask, request app = Flask(__name__) @app.route('/callback', methods=['POST']) def callback(): if not request.is_json: return 'Unsupported Media Type', 415 content = request.get_json() # TODO: handle callback return 'OK', 200, {'Content-Type': 'text/plain'} if __name__ == "__main__": app.run() ``` ```php header('Content-Type') !== 'application/json') { return response('Unsupported Media Type', 415); } $callback = $request->json()->all(); // TODO: handle callback return response('OK', 200)->header('Content-Type', 'text/plain'); } } // in routes/web.php: use Illuminate\Support\Facades\Route; use App\Http\Controllers\CallbackController; Route::post('/callback', [CallbackController::class, 'callback']); ``` ```java import com.fasterxml.jackson.databind.JsonNode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @PostMapping(path = "/callback", consumes = MediaType.APPLICATION_JSON, produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity callback(@RequestBody JsonNode json) { // TODO: handle callback // Return an "OK" response with a 200 status code return ResponseEntity.ok("OK"); } } ``` ## Step 3: Update internal state Once the authenticity of the callback data has been verified (see [Additional security](https://documentation.ixopay.com/docs/guides/production/additional-security)), your system can update its internal state to reflect the new state of the transaction. This could involve updating a database record, triggering a notification to a user, or performing some other action based on the current state of the transaction. * HTTP * Python: Flask * PHP: Laravel * Java: Spring Boot ```sh # See other languages ``` ```python from flask import Flask, request app = Flask(__name__) transaction_fetch(id): # fetch transaction from database transaction_update(transction): # update transaction in database send_email(customer, template_id): # send an email @app.route('/callback', methods=['POST']) def callback(): if not request.is_json: return 'Unsupported Media Type', 415 content = request.get_json() transaction_id = content.merchantTransactionId transaction_result = content.result # Update internal state based on current state of the transaction transaction = transaction_fetch(transaction_id) transcation.result = transaction_result transaction_update(transaction) if transaction.result == 'OK': send_email(transaction.customer, 'order_confirmed') return 'OK', 200, {'Content-Type': 'text/plain'} if __name__ == "__main__": app.run() ``` ```php header('Content-Type') !== 'application/json') { return response('Unsupported Media Type', 415); } $callback = $request->json()->all(); $transactionId = $callback->merchantTranscationId; $result = $callback->result; $transaction = Transaction::find($transactionId); if ($transaction === null) { return callbackHandledResponse(); } $transaction->result = $result; $transaction->save(); if($transaction->result === 'OK') { Mail::to($transaction->customer->email)->send( new OrderConfirmed($transaction->customerId)); } return callbackHandledResponse(); } private function callbackHandledResponse() { return response('OK', 200)->header('Content-Type', 'text/plain'); } } // in routes/web.php: use Illuminate\Support\Facades\Route; use App\Http\Controllers\CallbackController; Route::post('/callback', [CallbackController::class, 'callback']); ``` ```java import com.fasterxml.jackson.databind.JsonNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Repository; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @SpringBootApplication @RestController public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Autowired private TransactionRepository transactionRepository; @Autowired private JavaMailSender mailSender; @PostMapping(path = "/callback", consumes = MediaType.APPLICATION_JSON, produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity callback(@RequestBody JsonNode json) { String transactionId = node.get("merchantTransactionId") String result = node.get("result"); Transaction transaction = transactionRepository.findById(transactionId).orElse(null); if(transaction != null) { transaction.state = result; transactionRepository.save(transaction); if("OK".equals(transaction.state)) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(transaction.customerEmail); message.setSubject("Order confirmed."); message.setText( "Your payment has been received.\n\nThe order has now been confirmed."); mailSender.send(message); } } // Return an "OK" response with a 200 status code return ResponseEntity.ok("OK"); } @Entity @Table(name = "transaction") public static class Transaction { @Id private String id; @Column(name = "state") private String state; @Column(name = "customer_email") private String customerEmail; // ... } @Repository public static class TransactionRepository extends JpaRepository { } } ``` It is important to note that callbacks are not guaranteed to be delivered in real-time and may experience delays or even failures. If your initial request could not be processed, no transaction will be created; this is indicated by an initial transaction response with `success` set to `false`. If no transaction is created, IXOPAY platform will not send any callbacks. ## Conclusion In summary, using callbacks to update a payment transaction's state in the background can provide real-time updates on the status of the transaction without the need to constantly query the IXOPAY platform API. Follow the steps above to configure the callback URL, process the callback data, and update your internal state based on the current state of the transaction. ## Next steps Now that you have integrated IXOPAY platform's callbacks, you can look into … * … implementing [additional security](https://documentation.ixopay.com/docs/guides/production/additional-security) by validating the callback signature. * … [recurring payments](https://documentation.ixopay.com/docs/guides/getting-started/recurring-payments) to generate recurring revenue. * … [testing your setup](https://documentation.ixopay.com/docs/guides/getting-started/testing) to make sure you've set up everything correctly. * … exploring the [callback reference](https://documentation.ixopay.com/docs/reference/integration/callbacks) for detailed information on callback structure and usage.