Skip to content

Transfer SNS guide

Transfer SNS lets Wave initiate a transfer and receive asynchronous status updates through webhooks.

Base URL: https://api.gravv.xyz

Endpoint: POST /v1/transfer/sns

Transfer requirements (before POST /v1/transfer/sns)

You must satisfy all of the following:

  1. API credentials for the target environment.
  2. Required request headers: Api-Key and Idempotency-Key.
  3. A configured webhook endpoint and signing secret.
  4. A valid source internal account id provisioned for your business.
  5. An External Account (Recipient) created with POST /v1/external-accounts.
  6. External Account (Recipient) status must be active before transfer initiation.

Supported External Account (Recipient) types are bank_account and mobile_money. If destination.rail is supplied, the supported value is global_bank.

External Account (Recipient) setup

Create External Account (Recipient):

curl -X POST "https://api.gravv.xyz/v1/external-accounts" \
  -H "Api-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "payee_type": "mobile_money",
    "account_name": "Ada Lovelace",
    "phone_number": "+254700000000",
    "currency": "KES",
    "institution_country_iso_code": "KE",
    "customer_id": "<CUSTOMER_ID>"
  }'

Expected creation response includes External Account (Recipient) status:

{
  "id": "9a2e5d10-1111-1111-1111-111111111111",
  "payee_type": "mobile_money",
  "account_name": "Ada Lovelace",
  "status": "waiting_approval",
  "currency": "KES",
  "customer_id": "c0ffee00-2222-2222-2222-222222222222"
}

Use this External Account (Recipient) as transfer destination only after status becomes active.

waiting_approval status (important)

The transfer service enforces an active-only destination rule. If the External Account (Recipient) is still waiting_approval, transfer initiation is blocked until approval is completed.

Status behavior:

  • waiting_approval — parked for maker-checker review; not transfer-targetable
  • active — transfer-targetable
  • rejected — terminal non-transferable

In Gravv services, approval decisions are handled by approver roles (admin/owner) and waiting_approval is resolved by an explicit approve/reject action. For this integration, always check recipient status and proceed only when it is active.

Transfer flow

  1. Create the External Account (Recipient) destination.
  2. Wait for External Account (Recipient) status to become active.
  3. Call POST /v1/transfer/sns with your source account, active external account recipient, and amount.
  4. Store the returned reference, transaction_id, and your client_reference if supplied.
  5. Process transfer.status.* webhook events until the transfer reaches a terminal status.

Request

curl -X POST "https://api.gravv.xyz/v1/transfer/sns" \
  -H "accept: application/json" \
  -H "Api-Key: <YOUR_API_KEY>" \
  -H "Idempotency-Key: <YOUR_IDEMPOTENCY_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "source_type": "internal_account", "id": "<SOURCE_ACCOUNT_ID>" },
    "destination": { "destination_type": "external_account", "id": "<RECIPIENT_ID>" },
    "amount": "100.00",
    "additional_information": {
      "remitter_full_name": "John Doe",
      "remitter_country": "US",
      "remitter_document_number": "A123456789",
      "remitter_document_issue_date": "2020-01-15",
      "remitter_document_expiry_date": "2030-01-15",
      "remitter_date_of_birth": "1990-06-25",
      "remitter_address": {
        "address_line1": "123 Main Street",
        "city": "New York",
        "state": "NY",
        "postal_code": "10001",
        "country": "US"
      },
      "remitter_nationality": "US"
    },
    "description": "Wave payout",
    "customer_id": "<CUSTOMER_ID>",
    "client_reference": "wave-2026-06-18-001"
  }'
Field Required Description
Api-Key Yes API credential header.
Idempotency-Key Yes Unique key to safely deduplicate retries for the same transfer request.
source.source_type Yes Must be internal_account.
source.id Yes Source internal account id provisioned for your business.
destination.destination_type Yes Must be external_account.
destination.id Yes Id of an active External Account (Recipient) in your tenant.
destination.rail No Payment rail for the recipient payout. Use global_bank when supplied.
amount Yes Decimal amount as a string.
additional_information No Supplemental remitter information when required by payout rail/compliance.
description No Description for the transfer.
customer_id No Customer identifier when required by your integration. You can find it in Dashboard Manage → API Keys.
client_reference No Your correlation reference. Echoed back on webhook events when supplied.

Response

A successful request returns the accepted transfer record. Final status is delivered asynchronously by webhook.

{
  "reference": "5f8d0a11-4444-4444-4444-444444444444",
  "transaction_id": "8d6a8c2b-5555-5555-5555-555555555555",
  "transfer_status": "pending",
  "amount": "100.00",
  "local_amount": null,
  "local_currency": "USD",
  "date_created": "2026-06-18T10:15:30Z"
}

Lifecycle and webhook handling

Transfer SNS uses these status events:

pending ──▶ completed
      └───▶ failed
  • transfer.status.pending — the transfer was accepted and is processing.
  • transfer.status.completed — terminal success.
  • transfer.status.failed — terminal failure.

Use event_group_id, reference, and client_reference to correlate webhook events with the transfer request.

Example status webhook:

{
  "event_category": "transfer",
  "event_type": "transfer.status.completed",
  "event_group_id": "7c9e6679-3333-3333-3333-333333333333",
  "timestamp": "2026-06-18T10:15:30Z",
  "event_data": {
    "reference": "5f8d0a11-4444-4444-4444-444444444444",
    "status": "completed",
    "amount": "100.00",
    "client_reference": "wave-2026-06-18-001",
    "remark": null,
    "tx_hash": null,
    "client_metadata": null
  }
}

Webhook handlers should:

  1. Verify the webhook signature before processing.
  2. Match events by reference and client_reference.
  3. Update local state only for the received status transition.
  4. Return 2xx after successful processing.

Common failure cases

Common error responses include:

  • 400 for validation errors, missing required fields, unsupported external account recipient types, inactive recipients, an unsupported destination.rail, or source/destination mismatches.
  • 401 for missing or invalid credentials.
  • 403 when the caller lacks the required transfer permission.

External Account (Recipient) setup can also fail validation if a bank_account recipient is missing account_number, a mobile_money recipient is missing phone_number, or required fields such as payee_type, account_name, currency, or customer_id are absent.

See the API reference for complete schemas and examples.