Payment Tracking
Create a payment tracking in order to validate and automate payments.
POST
POST https://api.depay.com/v2/payments
Returns a 201
status code if payment tracking was created from scratch.
Returns a 200
status code if payment tracking already existed before.
This method implements idempotency. It will not create multiple tracking instances but will continue to provide the information for the given payment if the request is executed repeatedly.
It is required to setup a callback
, as the payment tracker needs to make sure to persist the payment result with your systems before releasing the user to a confirmation.
Payment status starts with pending
and changes to either success
or failed
(see Payment Data Structure).
Make sure to re-attempt the POST
request in case the request fails or returns anything but a 200/201
.
Example Request
{
"blockchain": "ethereum",
"transaction": "0xd4a9424440f6010af1bec311dda4e23d4f0016f4cc215da84a41650150ecb8b7",
"sender": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"nonce": "1",
"receiver": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"token": "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb",
"amount": "822.5",
"commitment": "confirmed",
"confirmations": 1,
"after_block": "13609144",
"secret_id": "74417770-e6ac-4ae8-b027-0657600d7bad",
"integration_id": "ef223b83-86c9-423f-9a0e-47a079d20f9d",
"callback": "https://webhook.site/4d4cd30f-d393-40f0-b909-85578a722ad7",
"forward_to": "https://example.com/continue/after/74417770-e6ac-4ae8-b027-0657600d7bad",
"payload": { "somekey": "somevalue" }
}
Required Attributes
blockchain
- The name of the blockchain
sender
- The address of the payment sender.
nonce
- The sender nonce of the expected payment transaction.
receiver
- The receiver address of the expected payment.
token
- The address of the token expected as payment.
amount
- Human readable expected payment amount (as string, to prevent rounding issues).
after_block
- The block after which to expect the payment to happen. The current block right before the payment was submitted.
secret_id
- Secret uuid referencing the payment attempt in your system. Do not expose this to the public! secret_id needs to be unguessable!
callback
- Secret callback url. Needs to be hidden from the public and needs to be unguessable. Will be called once the payment status completes either with success
or failed
.
Optional Attributes
commitment
- Default: confirmed
. The level of commitment required: confirmed
(the transaction has been at least confirmed once by the network) or finalized
(highly unlikely that it will be reversed or altered). If finalized
is set, you can't set confirmations
. finalized
can take multiple minutes on some blockchains (e.g. Ethereum). Only use finalized
for high-value payment transactions or fallback to control the individual amount of confirmations
.
confirmations
- Default: 1
. The amount of block confirmations required before considering a payment completed. If finalized
(commitment) is set, you can't set confirmations
.
transaction
- The transaction id/hash performing the payment. If transaction id is not provided payment will be tracked as a trace (before payment is actually made).
integration_id
- Assigns incoming payments to the given integration_id.
payload
- Allows to store additional, limited, payload information alongside the payment.
forward_to
- URL used to forward users to, once payment tracking is completed. Users typically get to retry payments until they succeed and are only forwarded if payments succeed (default).
forward_on_failure
- Boolean indicating if a user is to be forwarded to forward_to
also in the case of a failed
payment. If set to false
user gets to retry the payment until it succeeds (recommended).
fee_amount
- Expected payment fee amount. Human readable. As string, to prevent rounding issues.
fee_receiver
- The receiver address of the fee (if fee has been configured).
Example Response
{
"status": "pending",
"failed_reason": undefined,
"blockchain": "ethereum",
"transaction": "0xd4a9424440f6010af1bec311dda4e23d4f0016f4cc215da84a41650150ecb8b7",
"sender": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"nonce": "1",
"receiver": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"token": "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb",
"decimals": 18,
"commitment": "confirmed",
"confirmations": 1,
"after_block": "13609144",
"amount": "822.5",
"payload": {
"somekey": "somevalue"
},
"secret_id": "74417770-e6ac-4ae8-b027-0657600d7bad",
"callback": "https://webhook.site/4d4cd30f-d393-40f0-b909-85578a722ad7",
"forward_to": "https://example.com/continue/after/74417770-e6ac-4ae8-b027-0657600d7bad",
"forward_on_failure": false,
"confirmed_at": null,
"created_at": "2021-11-25T11:17:13.833Z",
"updated_at": "2021-11-25T11:17:13.833Z"
}
Callback
Once a payment changes state from pending
to either success
or failed
your system will retrieve a callback to the endpoint provided with callback
.
The callback request uses the POST
method.
Respond with 200
if your system is ready immediately upon response to handle the redirected user to perform next steps in your system.
Respond with 202
if your system accepts the callback, but is performing background processing that needs to be finished before the user is supposed to be redirected/released to the next step in your user flow.
In the case of performing background processing you need to make sure to perform an additional request to release the user.
Other codes but 200
or 202
will be considered a failed callback and will be retried up to 25 times over approx. 21 days. After that you will need to retrieve the data for the payment from your systems with another payment request.
Payment callbacks will retry failures with an exponential backoff using the formula (retry_count ** 4) + 15 + (rand(30) * (retry_count + 1)) (i.e. 15, 16, 31, 96, 271, ... seconds + a random amount of time)
.
Make sure you evaluate the status
of the payment callback in order to decide what to do in your systems next.
Example Request
POST <YOUR PROVIDED CALLBACK ENDPOINT>
{
"status": "success",
"failed_reason": undefined,
"blockchain": "ethereum",
"transaction": "0xd4a9424440f6010af1bec311dda4e23d4f0016f4cc215da84a41650150ecb8b7",
"sender": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"nonce": "1",
"receiver": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"token": "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb",
"decimals": 18,
"commitment": "confirmed",
"confirmations": 1,
"after_block": "13609144",
"amount": "822.5",
"payload": {
"somekey": "somevalue"
},
"secret_id": "74417770-e6ac-4ae8-b027-0657600d7bad",
"callback": "https://webhook.site/4d4cd30f-d393-40f0-b909-85578a722ad7",
"forward_to": "https://example.com/continue/after/74417770-e6ac-4ae8-b027-0657600d7bad",
"forward_on_failure": false,
"confirmed_at": "2021-11-25T12:54:52.332Z",
"created_at": "2021-11-25T11:17:13.833Z",
"updated_at": "2021-11-25T11:17:13.833Z"
}
Learn more about the Payment Data Structure
Validate Requests
In order to validate the origin of the payment callback request, and to make sure the payment callback request did not originate from another third party, you need to:
Validate that the provided
secret_id
in the callback request matches with what you've stored within your systems.(Optional) Validate the
x-signature
header to also cryptographically ensure the request came from DePay's APIs.
Validate Request Signatures
To start validating callback request signatures, retrieve your personal API public key via DePay App > API > Issue API Public Key.
Store the API public key and use it to validate payment callbacks in your systems.
Once you've issued an API public key, DePay APIs will start sending a signature (dedicated to your account) along with callback payloads.
DePay uses RSA-PSS, salt length 64, SHA256 to sign callback bodies, sending the signature base64 safe url encoded via the x-signature
header.
Pass Integration ID
In order for DePay's APIs to use your own account api secret to sign request bodies, you need to make sure to create an integration via DePay App > Integrations > New
and to pass the integration
id for every tracking request via payload
:
POST https://api.depay.com/v2/payments
//...
payload: {
//...
integration: 'MY-INTEGRATION-ID'
}
Javascript & Node
You can use the following libraries to verify signatures sent by DePay APIs:
Ruby
Following an example to verify signatures in Ruby/Rails:
public_key = OpenSSL::PKey::RSA.new(stored_public_key)
signature_decoded = Base64.urlsafe_decode64(request.headers["X-Signature"])
data = request.raw_post
verified = public_key.verify_pss("SHA256", signature_decoded, data, salt_length: :auto, mgf1_hash: "SHA256")
PHP
Following an example to verify signatures in PHP:
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\PublicKeyLoader;
$signature = $request->get_header('x-signature');
$signature = str_replace("_","/", $signature);
$signature = str_replace("-", "+", $signature);
$key = PublicKeyLoader::load($public_key)->withHash('sha256')->withPadding(RSA::SIGNATURE_PSS)->withMGFHash('sha256')->withSaltLength(64);
$key->verify($request->get_body(), base64_decode($signature));
Other programming languages
You can read up on how to verify RSA PSS signatures in other programming languages: here.
Background Processing
In the case you need to perform additional background processing upon recieving the payment callback, you will need to control the release of the user explicitly.
If you do need to perform background processing that takes longer than 5 seconds respond with a 202
upon the payment callback request, otherwise respond with a 200
, release the user immediately upon the callback request and skip the entire "Background Processing" section.
Execute your background processing and once you're done and ready to further handle the user within your user flow, make the following request:
POST https://api.depay.com/v2/payments/release
This will forward the user to configured forward_to
(which can be set upon payment tracking creation). If the forward_to
is not set, the payment widget will turn into a "completed state" and allows the user to close the payment widget.
Make sure to re-attempt the POST
request in case the request fails or returns anything but a 200
.
Example Request
{
"blockchain": "ethereum",
"transaction": "0xd4a9424440f6010af1bec311dda4e23d4f0016f4cc215da84a41650150ecb8b7",
"sender": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"nonce": "1",
"secret_id": "74417770-e6ac-4ae8-b027-0657600d7bad"
}
Required Attributes
blockchain
- The name of the blockchain
transaction
- The transaction id expected to perform the payment
sender
- The address of the payment sender.
nonce
- The sender nonce of the expected payment transaction.
secret_id
- Secret uuid referencing the payment identifcator in your system. Do not expose this to the public! secret_id needs to be unguessable!
GET
GET https://api.depay.com/v2/payments/{secret_id}
We recommend to only rely on the payment callback
while creating the payment tracking, but you can also retrieve the payment status via a GET request.
Just make sure you don't run into API call limitations that way and use reasonable pauses between requests (we recommend 1 minute polling).
Example Request
GET https://api.depay.com/v2/payments/45b55d24-60d0-45ba-9800-c61835494dc8-3
Example Response
{
"status": "success",
"failed_reason": undefined,
"blockchain": "ethereum",
"transaction": "0xd4a9424440f6010af1bec311dda4e23d4f0016f4cc215da84a41650150ecb8b7",
"sender": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"nonce": "1",
"receiver": "0x29b0d4cb9cFfEB360067199cf026dfD4854A8aB0",
"token": "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb",
"decimals": 18,
"commitment": "confirmed",
"confirmations": 1,
"after_block": "13609144",
"amount": "822.5",
"payload": {
"somekey": "somevalue"
},
"secret_id": "74417770-e6ac-4ae8-b027-0657600d7bad",
"callback": "https://webhook.site/4d4cd30f-d393-40f0-b909-85578a722ad7",
"forward_to": "https://example.com/continue/after/74417770-e6ac-4ae8-b027-0657600d7bad",
"forward_on_failure": false,
"confirmed_at": "2021-11-25T12:54:52.332Z",
"created_at": "2021-11-25T11:17:13.833Z",
"updated_at": "2021-11-25T11:17:13.833Z"
}
Learn more about the Payment Data Structure