Skip to content

Callbacks

Callbacks allow you to receive notifications whenever the state on a transaction changes (or something significant happens to it).

To set up Callbacks for the transactions, specify a callback_url in the Integration settings or send it in the payment request. Whenever something significant happens to the transaction that we think it needs notification of (typically a state change), we make an HTTP POST request to your callback_url with the object_id in the body of the request.

Note

Callbacks are asynchronous and not recommended for time-critical applications. It is very much possible and even likely that callbacks reach your application out-of-order and that they get duplicated. For time-critical applications, we advise using the API reconciliation to poll our system for updates.

Configure Callbacks

To configure your callbacks, go to Account settingsIntegrationCallbacks and add a callback URL.

Callback Requests

We send to your callback_url the HTTP request with the following characteristics:

  • a POST request;
  • with Request Body in JSON API format (the same as in the API);
  • contains the object type and id in the body of the request (not as URL-parameters).
{
    "data": {
        "type": "payment-invoices",
        "id": "cpi_yv1RgJ2l8ty2AxIs",
        "attributes": {
        "serial_number": "yv1RgJ2l8ty2AxIs",
        "status": "processed",
        "resolution": "ok",
        "moderation_required": false,
        "amount": 22,
        "payment_amount": 22,
        "currency": "USD",
        "service_currency": "USD",
        "reference_id": "da1b0b9d-c249-4f6e-9949-2a2f2d4b1758",
        "test_mode": true,
        "fee": 0,
        "deposit": 22,
        "processed": 1592232071,
        "processed_amount": 22,
        "refunded_amount": null,
        "processed_fee": 0,
        "processed_deposit": 22,
        "metadata": {
            "merchant_url": "https://yu.test.this"
        },
        "flow_data": {
            "action": "https://example.domain/hpp/cgi_Q0fYDFaHlqb5MCdl",
            "method": "GET",
            "params": [],
            "metadata": {
            "sid": "cgi_Q0fYDFaHlqb5MCdl",
            "token": "eyJ0eXAiOiJKV1QiLRiGCg2O...nGE7E"
            }
        },
        "flow": "hpp",
        "payment_flow": "charge",
        "created": 1592232050,
        "updated": 1592232071,
        "payload": {
            "token": null,
            "client_ip": "54.36.117.30",
            "payment_card": {
            "last": "0000",
            "mask": "512381******0000",
            "brand": "mastercard",
            "first": "512381",
            "holder": null,
            "expiry_year": "33",
            "issuer_name": "FIRST DATA CORPORATION",
            "expiry_month": "11",
            "issuer_country": "US"
            }
        },
        "description": null,
        "descriptor": null,
        "callback_url": "https://test.doc.home",
        "return_url": "https://example.com",
        "return_urls": {
            "fail": "https://example.com/sorry-page",
            "pending": "https://example.com/wait-for-it-page",
            "success": "https://example.com/thank-you-page"
        },
        "original_data": null,
        "rrn": null,
        "approval_code": null,
        "reserved_amount": null,
        "reserve_expires": null,
        "unreserved": null,
        "source": null,
        "callback_logs": []
        },
        "relationships": {
        "payment-service": {
            "data": {
            "type": "payment-services",
            "id": "payment_card_usd_hpp"
            }
        },
        "payment-method": {
            "data": {
            "type": "payment-methods",
            "id": "payment_card"
            }
        },
        "customer": {
            "data": null
          }
        },
        "links": {
        "self": "/api/payment-invoices/cpi_yv1RgJ2l8ty2AxIs"
        }
    }
}
{
"data": {
    "type": "payout-invoices",
    "id": "cpoi_sIzOuMKJg98J22NC",
    "attributes": {
    "serial_number": "sIzOuMKJg98J22NC",
    "status": "processed",
    "resolution": "ok",
    "amount": 100,
    "payout_amount": 100,
    "currency": "USD",
    "service_currency": "USD",
    "service_amount": 100,
    "service_payout_amount": 100,
    "reference_id": "45284707-d243-439e-8b41-d657322e693b",
    "test_mode": true,
    "description": null,
    "fee": 0,
    "writeoff": 100,
    "exchange_rate": 1,
    "processed": 1621335982,
    "processed_amount": 100,
    "processed_fee": 0,
    "processed_writeoff": 100,
    "metadata": [],
    "created": 1621335974,
    "updated": 1621335982,
    "fields": {
        "card_number": "512381******0000"
    },
    "callback_url": "https://test.doc.home",
    "source": "merchant_dashboard",
    "callback_logs": [],
    "payouts": [
            {
                "id":"po_4a3Bgz6YR3cRjW6k",
                "rrn":null,
                "amount":72.5,
                "currency":"USD",
                "status":"processed"
            },
            {
                "id":"po_3cR4z6kgYR6aj3BW",
                "rrn":null,
                "amount":27.5,
                "currency":"USD",
                "status":"processed"
            }
        ]
    },
    "relationships": {
    "payout-service": {
        "data": {
        "type": "payout-services",
        "id": "payment_card_usd"
        }
    },
    "payout-method": {
        "data": {
        "type": "payout-methods",
        "id": "payment_card"
        }
    },
    "customer": {
        "data": null
    }
    },
    "links": {
    "self": "/api/payout-invoices/cpoi_sIzOuMKJg98J22NC"
    }
  }
}

The Callback request body contains the body of the related invoice transaction.

To check invoice statuses, please read:

Signature

Spoynt signs data using secret keys in the X-Signature callback request header.

The X-Signature generation algorithm

<?php
$secret="yourPrivateKey";    
$callbackData='{"data":{"type":"payment-invoices","id":"cpi_exampleID","attributes":{"serial_number":"exampleID","status":"processed","resolution":"ok","moderation_required":false,"amount":1000,"payment_amount":1000,"currency":"USD","service_currency":"USD","reference_id":"yourReferenceId","test_mode":true,"fee":38,"deposit":962,"processed":1647077297,"processed_amount":1000,"refunded_amount":null,"processed_fee":38,"processed_deposit":962,"metadata":[],"flow_data":{"action":"https:\/\/pay.example.com\/hpp\/cgi_exampleId","method":"GET","params":[],"metadata":{"sid":"cgi_exampleId","token":"bearerToken-for-H2H-api-auth"}},"flow":"hpp","payment_flow":"charge","created":1647077285,"updated":1647077297,"payload":{"token":"token-if-tokenize-true","auth_type":"card","client_ip":"192.168.0.1","payment_card":{"last":"0000","mask":"512381******0000","brand":null,"first":"512381","holder":"Test Customer","network":"mastercard","expiry_year":"99","issuer_name":"FIRST DATA CORPORATION","expiry_month":"09","issuer_country":"US"}},"description":null,"descriptor":null,"callback_url":"https:\/\/your.callback.url","return_url":null,"return_urls":{"fail":"https:\/\/your.fail-return.url","pending":"https:\/\/your.default-return.url","success":"https:\/\/your.success-return.url"},"original_data":null,"rrn":null,"approval_code":null,"reserved_amount":null,"reserve_expires":null,"unreserved":null,"source":"merchant_api","callback_logs":[],"charged_back_amount":null,"resolution_message":null,"hpp_url":"https:\/\/api.example.com\/hpp?cpi=cpi_exampleID"},"relationships":{"payment-service":{"data":{"type":"payment-services","id":"payment_card_usd_hpp"}},"payment-method":{"data":{"type":"payment-methods","id":"payment_card"}},"customer":{"data":{"type":"customers","id":"cus_exampleID"}},"active-payment":{"data":{"type":"payments","id":"pay_exampleID"}}},"links":{"self":"\/api\/payment-invoices\/cpi_exampleID"}},"included":[{"type":"customers","id":"cus_exampleID","attributes":{"reference_id":"example-customer-id","email":null,"name":null,"phone":null,"description":null,"created":1646833439,"metadata":[],"avatar":"https:\/\/www.gravatar.com\/avatar\/exampleAvatarID?s=20&d=mm&r=g","archived":false,"processing_options":{"payment_enabled":true,"payout_enabled":true},"address":{"street":null,"country":null,"region":null,"city":null,"full_address":null,"post_code":null}},"relationships":{"commerce-account":{"data":{"type":"commerce-accounts","id":"coma_exampleID"}}}}]}';
$signature = base64_encode(sha1($secret . $callbackData . $secret, true));

echo $signature;
where:
$secret is one of your secret (private) keys: test or live,
$callbackData is raw JSON data,
$signature is a generation algorithm.

Note

To be sure you got data from Spoynt, you should generate a signature using the appropriate private key and raw JSON Сallback's data. The result is a base64 string that you should compare with Сallback's header signature. As an example:

B86Af35b/IfM0z0rGROHw5gVw14=

Timeouts

Spoynt uses 3 timeout types for Callbacks

  1. The Connection Timeout is the timeout for making an initial connection to the callback URL's HTTP server.
  2. Read Timeout: after the initial connection setting, still can be a possibility of an indefinite waiting period for reading data from the HTTP server.
  3. Total Callback Timeout: in addition to the above, Spoynt also checks the total execution time through the callback execution timeout.

The values for each timeout are as follows:

Timeout For test connection For live connection
Connection Timeout 10,000 мs 20,000 мs
Read Timeout 10,000 мs 20,000 мs
Execution Timeout 20,000 мs 60,000 мs

Callback delivery and auto retries

The HTTP code received in the response to Callbacks defines system further actions.

Code Definition Further actions
200 Successful delivery No retry required, next scheduled delivery attempts cancelled
429 Too Many Requests, Callback is undelivered The system forcibly stops delivery retries and cancels next scheduled delivery attempts.

If the system obtains any other status code in the response, we assume the delivery of Callback failed. As default, the failed callback request is resent with an increasing delay between each attempt:

  • the 1st retry 1 minute after the initial attempt,
  • the 2nd retry 2 minutes after the 1st retry,
  • the 3rd retry 3 minutes after the 2nd retry,
  • the 4th retry 4 minutes after the 3rd retry,
  • the 5th retry 5 minutes after the 4th retry,
  • the 6th retry 6 minutes after the 5th retry,
  • and so on, up to 100 attempts or up to receiving 200 or 429 HTTP status code.

We can set up the delay between retries and the number of attempts

Contact our support team that we may find the most appropriate settings for your account.

Note

Also, you could resend a callback manually if you wish to sync your data immediately. Go to Transaction overviewCallbacks, select the Callback and use the button on the right side to resend it.

Resend Callback

Duplicate handling

Due to callback retries, your app may receive the same callback more than once. Ensure idempotency of the callback by detecting such duplicates within your app.

Idempotence is the property of certain operations in mathematics and computer science, whereby they can be applied multiple times without changing the result beyond the initial application. In short, making multiple identical requests has the same effect as making a single request.

To control processing idempotency use by examining the id parameter in callback request data since its value is unique to operation and thus identifies it.

It isn't a severe issue: if your app's actions are always idempotent, you don't need to worry much about concurrency.

Out-of-Order Delivery

Callbacks can also arrive out-of-order due to issues such as network delays or failures. However, you can determine the order of the events by examining the updated attribute of the resource sent by the callback. updated is a timestamp value that updated for every change made to the transaction.

Batching

We batch callbacks for the same object that are relatively close together. For example, if a payment goes from a state created to pending to processed immediately, you only receive one callback, and when you look up the status of the payment, it will be processed.

It prevents us from overwhelming your servers with HTTP requests, and it prevents your app from having to build complicated logic around subsequent status changes. Your app should only care about the latest transaction state.

IPs whitelist

To increase the security of the interaction between the Spoynt platform and your server, use the white list of IP addresses.

Learn More →