PACS.008 Payment Initiation

ISO 20022 PACS.008 Customer Credit Transfer API documentation for submitting payments via the StrongholdNET network.

PACS.008 Payment Initiation API Documentation

Overview

The PACS.008 endpoint processes ISO 20022 Customer Credit Transfer messages and returns PACS.002 Payment Status Report responses. This endpoint provides standards-compliant payment processing across multiple payment networks.

Endpoint: POST /v1/pacs008/{network}

Supported Networks:

  • shnet - Stronghold Network

Content Types:

  • Request: application/xml or text/xml
  • Response: application/xml

Authentication

All requests require API key authentication via the SHNET-API-KEY header:

POST /v1/pacs008/shnet
Content-Type: application/xml
SHNET-API-KEY: your-api-key-here

Request Format (PACS.008)

Minimal PACS.008 Example for SHNET Network

Field Markers:

  • [VALIDATION] = Required for format validation to pass
  • [TRANSFER] = Required for transfer creation (includes all [VALIDATION] requirements)
  • [OPTIONAL] = Not required but recommended for tracking/auditing
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.13">
  <!-- [VALIDATION] FIToFICstmrCdtTrf: Root payment element - REQUIRED -->
  <FIToFICstmrCdtTrf>

    <!-- [VALIDATION] GrpHdr: Group Header - REQUIRED -->
    <GrpHdr>
      <!-- [VALIDATION] MsgId: SHNET format {UnixTimestamp}{Random10Hex} - REQUIRED
           - First 10 chars: Unix timestamp in UTC (seconds since epoch)
           - Last 10 chars: Random hexadecimal (0-9A-F uppercase)
           - Timestamp must be no older than 24 hours (prevents replay attacks)
           - Timestamp must be no more than 5 minutes in the future (clock skew tolerance)
           - Example: 1732675800A3F2B7E156 -->
      <MsgId>1732675800A3F2B7E156</MsgId>

      <!-- [OPTIONAL] CreDtTm: Message creation timestamp (ISO 8601)
           Not validated but recommended for auditing -->
      <CreDtTm>2024-11-27T10:30:00Z</CreDtTm>

      <!-- [OPTIONAL] NbOfTxs: Number of transactions (Phase 1: always 1)
           Not validated -->
      <NbOfTxs>1</NbOfTxs>
    </GrpHdr>

    <!-- [VALIDATION] CdtTrfTxInf: Credit Transfer Transaction - REQUIRED
         Phase 1: Must contain exactly ONE transaction (multiple not supported) -->
    <CdtTrfTxInf>

      <!-- [OPTIONAL] PmtId: Payment Identification - STRONGLY RECOMMENDED for tracking -->
      <PmtId>
        <!-- [OPTIONAL] InstrId: Instruction ID - returned in PACS.002 response -->
        <InstrId>INSTR-001</InstrId>

        <!-- [OPTIONAL] EndToEndId: End-to-end ID - returned in PACS.002 response -->
        <EndToEndId>E2E-20241127-001</EndToEndId>

        <!-- [OPTIONAL] TxId: Transaction ID -->
        <TxId>TXN-001</TxId>
      </PmtId>

      <!-- [VALIDATION] IntrBkSttlmAmt: Settlement Amount - REQUIRED
           [TRANSFER] Ccy attribute: Currency code (ISO 4217) - REQUIRED for transfer
           SHNET limits: $0.01 to $10,000
           SHNET supports: USD only (converted to SHX token on Stellar) -->
      <IntrBkSttlmAmt Ccy="USD">100.00</IntrBkSttlmAmt>

      <!-- [TRANSFER] DbtrAcct: Debtor (Source) Account - REQUIRED for transfer creation
           This is the ACH source account that will be debited -->
      <DbtrAcct>
        <Id>
          <Othr>
            <!-- [TRANSFER] Account number or identifier - REQUIRED -->
            <Id>123456789</Id>
          </Othr>
        </Id>
      </DbtrAcct>

      <!-- [TRANSFER] CdtrAcct: Creditor (Destination) Account - REQUIRED for transfer creation
           This is the Stellar destination that will receive the funds -->
      <CdtrAcct>
        <Id>
          <Othr>
            <!-- [TRANSFER] Stellar Public Key (56 characters starting with G) - REQUIRED
                 Example: GDSTELLARPUBLICKEY123XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -->
            <Id>GDSTELLARPUBLICKEY123XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</Id>
          </Othr>
        </Id>

        <!-- [OPTIONAL] Nm: Stellar memo/reference - Recommended for tracking
             Max 28 characters, included in Stellar transaction memo -->
        <Nm>Order #12345</Nm>
      </CdtrAcct>

    </CdtTrfTxInf>
  </FIToFICstmrCdtTrf>
</Document>

Response Format (PACS.002)

Success Response - Acceptance (HTTP 201)

When a payment is accepted for processing:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.15">
  <FIToFIPmtStsRpt>
    <!-- Group Header -->
    <GrpHdr>
      <MsgId>RESP-1732704600-A1B2C3D4</MsgId>
      <CreDtTm>2024-11-27T10:30:00Z</CreDtTm>
    </GrpHdr>
    
    <!-- Transaction Information and Status -->
    <TxInfAndSts>
      <OrgnlEndToEndId>E2E-ACME-INV12345</OrgnlEndToEndId>
      <OrgnlInstrId>INSTR-2024-11-27-001</OrgnlInstrId>
      <TxSts>ACCP</TxSts>
      <StsRsnInf>
        <AddtlInf>TransferId: 01939b9d-4f9e-7654-b123-456789abcdef</AddtlInf>
      </StsRsnInf>
    </TxInfAndSts>
  </FIToFIPmtStsRpt>
</Document>

Status Code: ACCP - Accepted Customer Profile (Payment has been accepted)


Rejection Response - Validation Error (HTTP 400)

When a payment is rejected due to validation errors:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.15">
  <FIToFIPmtStsRpt>
    <GrpHdr>
      <MsgId>RESP-1732704601-E1F2G3H4</MsgId>
      <CreDtTm>2024-11-27T10:30:01Z</CreDtTm>
    </GrpHdr>

    <TxInfAndSts>
      <OrgnlEndToEndId>E2E-ACME-INV12345</OrgnlEndToEndId>
      <OrgnlInstrId>INSTR-2024-11-27-001</OrgnlInstrId>
      <TxSts>RJCT</TxSts>
      <StsRsnInf>
        <Rsn>
          <Cd>AM09</Cd>
        </Rsn>
        <AddtlInf>Amount 15000 exceeds SHNET maximum of 10000</AddtlInf>
      </StsRsnInf>
    </TxInfAndSts>
  </FIToFIPmtStsRpt>
</Document>

Status Code: RJCT - Rejected Reason Code: AM09 - Wrong Amount


Rejection Response - XML Parsing Error (HTTP 400)

When the XML is malformed or doesn't match the schema:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.15">
  <FIToFIPmtStsRpt>
    <GrpHdr>
      <MsgId>RESP-1732704602-K1L2M3N4</MsgId>
      <CreDtTm>2024-11-27T10:30:02Z</CreDtTm>
    </GrpHdr>
    
    <TxInfAndSts>
      <OrgnlEndToEndId>NOTPROVIDED</OrgnlEndToEndId>
      <OrgnlInstrId>NOTPROVIDED</OrgnlInstrId>
      <TxSts>RJCT</TxSts>
      <StsRsnInf>
        <Rsn>
          <Cd>FF01</Cd>
        </Rsn>
        <AddtlInf>Invalid XML: Element 'GrpHdr' not found in namespace 'urn:iso:std:iso:20022:tech:xsd:pacs.008.001.13'</AddtlInf>
      </StsRsnInf>
    </TxInfAndSts>
  </FIToFIPmtStsRpt>
</Document>

Reason Code: FF01 - Invalid File Format


Rejection Response - Invalid Stellar Account (HTTP 400)

When the Stellar public key format is invalid:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.002.001.15">
  <FIToFIPmtStsRpt>
    <GrpHdr>
      <MsgId>RESP-1732704603-P1Q2R3S4</MsgId>
      <CreDtTm>2024-11-27T10:30:03Z</CreDtTm>
    </GrpHdr>
    
    <TxInfAndSts>
      <OrgnlEndToEndId>E2E-ACME-INV12345</OrgnlEndToEndId>
      <OrgnlInstrId>INSTR-2024-11-27-001</OrgnlInstrId>
      <TxSts>RJCT</TxSts>
      <StsRsnInf>
        <Rsn>
          <Cd>AC01</Cd>
        </Rsn>
        <AddtlInf>Invalid Stellar public key format in creditor account. Expected valid Ed25519 public key (56 characters starting with 'G')</AddtlInf>
      </StsRsnInf>
    </TxInfAndSts>
  </FIToFIPmtStsRpt>
</Document>

Reason Code: AC01 - Incorrect Account Number


Status Codes

Transaction Status Codes (TxSts)

CodeNameDescription
ACCPAccepted Customer ProfilePayment accepted for processing
ACSCAccepted Settlement CompletedPayment settled successfully
RJCTRejectedPayment rejected
PDNGPendingPayment is pending
ACSPAccepted Settlement In ProcessSettlement is in progress

HTTP Status Codes

HTTP CodeScenarioPACS.002 Status
201Payment accepted successfullyACCP
400Validation error, XML parsing error, business logic errorRJCT
401Authentication failed (no PACS.002 sent)N/A
422Unexpected processing errorRJCT
500Internal server configuration errorRJCT

Reason Codes (Current Phase 2)

CodeNameDescriptionExample Scenario
FF01Invalid File FormatXML parsing/schema validation errorsMalformed XML, missing elements
AM09Wrong AmountAmount validation failedExceeds limits, negative amount
AC01Incorrect Account NumberAccount/routing number validation failedInvalid format, account doesn't exist
NARRNarrativeGeneric reason with custom explanationCatch-all for various errors
CUSTInvalid CurrencyCurrency code validation failedUnsupported currency
AG01Invalid AgentBIC/bank identification validation failedUnknown BIC code

Reason Codes (Planned for Phase 3)

Amount-Related Codes

  • AM04 - Insufficient Funds
  • AM05 - Duplication
  • AM18 - Invalid Amount

Account-Related Codes

  • AC03 - Invalid Creditor Account Number
  • AC04 - Closed Account Number
  • AC06 - Blocked Account
  • AC13 - Invalid Debtor Account Type

Agent-Related Codes

  • AG02 - Invalid Bank Operation Code
  • AG03 - Transaction Forbidden
  • AGNT - Incorrect Agent

Generic Codes

  • DUPL - Duplicate Payment
  • TECH - Technical Problem
  • UPAY - Undue Payment
  • RR04 - Regulatory Reason
  • LEGL - Legal Decision

Network-Specific Validation (shnet)

SHNET Message ID Format (REQUIRED)

Format: {UnixTimestamp}{Random10Hex}

  • Total Length: 20 characters
  • First 10 characters: Unix timestamp in seconds (e.g., 1732675800)
  • Last 10 characters: Random hexadecimal (0-9A-F uppercase)
  • Example: 1732675800A3F2B7E156

Validation Rules:

  • ✅ Message timestamp must be within 24 hours of current time (prevents replay attacks)
  • ✅ Message timestamp cannot be more than 5 minutes in the future (clock skew tolerance)
  • ❌ Old messages (>24h) are rejected to prevent replay attacks
  • ❌ Future messages (>5min ahead) are rejected

Generate Timestamp:

# Unix/Mac
date +%s

# PowerShell
[DateTimeOffset]::UtcNow.ToUnixTimeSeconds()

# Node.js
Math.floor(Date.now() / 1000)

# Python
import time; int(time.time())

Generate Full Message ID:

# Example in bash
echo "$(date +%s)$(openssl rand -hex 5 | tr '[:lower:]' '[:upper:]')"
# Output: 1732675800A3F2B7E156

Amount Validation

  • Minimum: $0.01
  • Maximum: $10,000
  • Precision: Up to 2 decimal places
  • Validation: Amount must be greater than zero

Currency Validation

  • Supported: USD only
  • Conversion: All USD transfers are converted to SHX tokens on the Stellar blockchain
  • Format: 3-letter currency code (e.g., USD)

Required Fields for Validation

Minimum fields to pass validation:

  1. FIToFICstmrCdtTrf - Root element
  2. GrpHdr/MsgId - SHNET format message ID
  3. CdtTrfTxInf - Exactly ONE transaction (multiple not supported in Phase 1)
  4. IntrBkSttlmAmt - Amount with Ccy attribute
  5. Amount > 0 and within limits

Required Fields for Transfer Creation

Additional fields needed to create a transfer:

  1. DbtrAcct/Id/Othr/Id - Source ACH account number
  2. CdtrAcct/Id/Othr/Id - Destination Stellar public key (56 chars, starts with G)

Optional but recommended:

  • PmtId/InstrId - Instruction ID (returned in PACS.002)
  • PmtId/EndToEndId - End-to-end ID (returned in PACS.002)
  • CdtrAcct/Nm - Stellar memo (max 28 characters)

NOT Required for SHNET

  • DbtrAgt - Debtor Agent (only required for FedWire)
  • CdtrAgt - Creditor Agent (only required for FedWire)
  • Dbtr - Debtor party details
  • Cdtr - Creditor party details

Error Scenarios & Examples

Scenario 1: Missing Required Field

Request: PACS.008 missing <MsgId>

Response (HTTP 400):

<TxSts>RJCT</TxSts>
<StsRsnInf>
  <Rsn><Cd>FF01</Cd></Rsn>
  <AddtlInf>Required field 'MsgId' is missing from GroupHeader</AddtlInf>
</StsRsnInf>

Scenario 2: Invalid SHNET Message ID Format

Request: PACS.008 with <MsgId>INVALID-MSG-ID</MsgId> (not in SHNET format)

Response (HTTP 400):

<TxSts>RJCT</TxSts>
<StsRsnInf>
  <Rsn><Cd>NARR</Cd></Rsn>
  <AddtlInf>Invalid SHNET message ID format. Expected: {UnixTimestamp}{Random10Hex} (e.g., 1730103000A3F2B7E156)</AddtlInf>
</StsRsnInf>

Scenario 3: Invalid Network

Request: POST /v1/pacs008/swift (unsupported network)

Response (HTTP 400):

<TxSts>RJCT</TxSts>
<StsRsnInf>
  <Rsn><Cd>NARR</Cd></Rsn>
  <AddtlInf>Network 'swift' is not supported. Supported networks: shnet</AddtlInf>
</StsRsnInf>

Scenario 4: Amount Too Large

Request: PACS.008 with amount exceeding $10,000

Response (HTTP 400):

<TxSts>RJCT</TxSts>
<StsRsnInf>
  <Rsn><Cd>AM09</Cd></Rsn>
  <AddtlInf>Amount 15000 exceeds SHNET maximum of 10000</AddtlInf>
</StsRsnInf>

Scenario 5: Invalid Stellar Public Key

Request: PACS.008 with invalid Stellar public key in <CdtrAcct> (e.g., too short, wrong prefix, invalid checksum)

Response (HTTP 400):

<TxSts>RJCT</TxSts>
<StsRsnInf>
  <Rsn><Cd>AC01</Cd></Rsn>
  <AddtlInf>Invalid Stellar public key format in creditor account. Expected valid Ed25519 public key (56 characters starting with 'G')</AddtlInf>
</StsRsnInf>

Best Practices

Message ID Generation for SHNET

IMPORTANT: SHNET requires a specific message ID format for security and replay protection.

Required Format: {UnixTimestamp}{Random10Hex}

  • Total: 20 characters
  • First 10 chars: Unix timestamp (seconds)
  • Last 10 chars: Random uppercase hexadecimal
  • Example: 1732675800A3F2B7E156

Generation Examples:

# Bash/Unix
echo "$(date +%s)$(openssl rand -hex 5 | tr '[:lower:]' '[:upper:]')"

# PowerShell
$ts = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$rnd = -join ((0..4) | ForEach-Object { '{0:X2}' -f (Get-Random -Max 256) })
"$ts$rnd"

# Node.js
const ts = Math.floor(Date.now() / 1000);
const rnd = crypto.randomBytes(5).toString('hex').toUpperCase();
`${ts}${rnd}`

Security:

  • Never reuse message IDs (enforced by timestamp + random)
  • Messages older than 24 hours are automatically rejected
  • Future-dated messages (>5min ahead) are rejected

End-to-End ID

  • Should be unique per payment instruction
  • Use for reconciliation and tracking
  • Format: E2E-{organization}-{reference}

Error Handling

  • Always check HTTP status code first
  • Parse PACS.002 response for detailed reason codes
  • Log OrgnlEndToEndId for correlation
  • Implement retry logic for transient errors (500, 503)

Testing

  1. Validate XML locally before sending
  2. Test with small amounts first
  3. Use sandbox environment for integration
  4. Keep test message IDs unique
  5. Test all error scenarios