Skip to main content

Transfer Funds to Sellers

In this guide we will show how marketplaces and other exchanges can use Payment Transfer Intents to send funds to seller accounts from an existing payment.

The intent pattern gives you a pre-generated transfer ID before any funds move, and lets you confirm or cancel the transfer as a separate step. See Payment Transfer Intents for a full explanation of states, concurrency rules, and webhook events.

Getting Started

To get started, you will need a Credova Account.

Get your Secret Key

Next you will need your Secret Key. Go to your Developers section and click Reveal for your Secret Key and copy the value.

Save the Secret Key as it will be used in the next steps of this guide.

Prerequisites

This guide assumes you have already completed:

Grab the payment id:

Payment
{
"id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
...
}

And your seller account IDs:

Seller Account
{
"id": "acc_w6dogDaHuU6h1N5e5vfXLUYf",
"name": "Test Seller",
"type": "seller",
...
}

Step 1 — Create the Intent

Call Create Payment Transfer Intent to initialize the transfer. The response is returned in pending status and includes a pre-generated payment_transfer_id you can store in your system before committing.

Create a Payment Transfer Intent
curl 'https://api.publicsquare.com/payment-transfer-intents' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"payment_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"currency": "USD",
"external_id": "e797ef3c-b586-4333-af90-7168d8427d85",
"items": [
{
"seller_account_id": "acc_w6dogDaHuU6h1N5e5vfXLUYf",
"external_id": "marketplace-line-1",
"amount": 10000,
"transfer_fee": 100
}
]
}'
Amounts are in cents. 10000 is $100.00.

You should see a response similar to:

Payment Transfer Intent (pending)
{
"id": "trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "pending",
"payment_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"payment_transfer_id": "trnf_7yFLQWACr3DYDSz1xpoEAVfdq",
"currency": "USD",
"total_amount": 10000,
"external_id": "e797ef3c-b586-4333-af90-7168d8427d85",
"items": [
{
"id": "trfi_int_AjkCFKAYiTsjghXWMzoXFPMxj",
"seller_account_id": "acc_w6dogDaHuU6h1N5e5vfXLUYf",
"payment_transfer_item_id": "trfi_AjkCFKAYiTsjghXWMzoXFPMxj",
"amount": 10000,
"transfer_fee": 100,
"external_id": "marketplace-line-1"
}
],
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
}

Store the id (trnf_int_*) and optionally the payment_transfer_id (trnf_*) in your system before proceeding.

Step 2 — Confirm the Intent

Call Confirm Payment Transfer Intent to submit the transfer. No request body is required.

Confirm Payment Transfer Intent
curl 'https://api.publicsquare.com/payment-transfer-intents/trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq/confirm' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>'

A successful response returns the intent in succeeded status in most cases:

Payment Transfer Intent (succeeded)
{
"id": "trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "test",
"status": "succeeded",
"payment_id": "pmt_2YKewBonG4tgk12MheY3PiHDy",
"payment_transfer_id": "trnf_7yFLQWACr3DYDSz1xpoEAVfdq",
"currency": "USD",
"total_amount": 10000,
"created_at": "2024-06-30T01:02:29.212Z",
"modified_at": "2024-06-30T01:02:29.212Z"
}

If the transfer is not yet confirmed, the intent returns in processing status instead. Final status will arrive via the transfer_intent:update webhook.

Step 3 — Listen for Completion

Credova sends a transfer_intent:update webhook on every status transition. The entity.status field in the webhook payload reflects the final state — use it to confirm completion regardless of what the confirm response returned.

Webhook: transfer_intent:update (succeeded)
{
"id": "evnt_5jxWRFNLCAWeegrkCAG3a9DGc",
"account_id": "acc_B518niGwGYKzig6vtrRVZGGGV",
"environment": "production",
"event_type": "transfer_intent:update",
"entity_type": "PaymentTransferIntent",
"entity_id": "trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq",
"entity": {
"id": "trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq",
"status": "succeeded",
"payment_transfer_id": "trnf_7yFLQWACr3DYDSz1xpoEAVfdq",
"total_amount": 10000,
...
},
"created_at": "2024-06-30T01:02:29.212Z"
}

A minimal handler that marks the intent as complete in your system:

Webhook handler (Node.js)
app.post('/webhooks/publicsquare', (req, res) => {
const event = req.body;

if (event.event_type === 'transfer_intent:update') {
const intent = event.entity;

if (intent.status === 'succeeded') {
// mark transfer complete in your system using intent.payment_transfer_id
} else if (intent.status === 'error') {
// intent expired or the transfer failed
}
}

res.sendStatus(200);
});

See Webhooks for delivery guarantees, signing, and retry behavior.

Polling (alternative to webhooks)

If you have not set up webhooks, you can poll Get Payment Transfer Intent until status is terminal (succeeded, cancelled, or error).

Poll intent status
curl 'https://api.publicsquare.com/payment-transfer-intents/trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq' \
-H 'X-API-KEY: <SECRET_API_KEY>'
Webhooks are strongly recommended over polling for production integrations. If the intent is in processing, it may take seconds to minutes to reach a terminal status.

Cancelling an Intent

An intent can only be cancelled while in pending status. Call Cancel Payment Transfer Intent:

Cancel Payment Transfer Intent
curl 'https://api.publicsquare.com/payment-transfer-intents/trnf_int_7yFLQWACr3DYDSz1xpoEAVfdq/cancel' \
-X 'POST' \
-H 'X-API-KEY: <SECRET_API_KEY>'

A successful cancel returns 204 No Content.

Attempting to cancel after the intent has been confirmed returns 400 Bad Request:

400 — intent not in pending
{
"type": "https://httpstatuses.com/400",
"title": "Bad Request",
"status": 400,
"detail": "Payment transfer intent is not in pending status"
}

Attempting to cancel while a confirm is already in flight returns 409 Conflict:

409 — confirm in progress
{
"type": "https://httpstatuses.com/409",
"title": "Conflict",
"status": 409,
"detail": "Confirmation already in progress"
}

Error Reference

HTTP StatusEndpointMeaning
404 Not Foundconfirm, cancel, GET /{id}Intent ID does not exist or belongs to a different account.
409 Conflictconfirm, cancelThe opposing operation (confirm or cancel) is already in flight. Retry after a short delay.
400 Bad RequestcancelIntent is not in pending status.
202 AcceptedconfirmTransfer submitted but is not confirmed yet. The intent is processing and its terminal state will arrive via webhook.

See Payment Transfer Intents for a deeper explanation of the concurrency model.

Conclusion

Following this guide, you have successfully transferred funds to a seller's account using Payment Transfer Intents.

Follow these guides to learn more: