Developer Quick-Start

Overview

This guide walks through implementing the BaaSReferralGateway integration end-to-end, in the order you'll build it. Before starting, confirm you have all items from the Prerequisites & Onboarding checklist.


Step 1 - Verify Connectivity

Before writing any enrollment code, confirm your IP is allowlisted and your credentials are working.

GET {QA_BASE_URL}/healthcheck
X-GD-RequestId: {fresh-uuid}

Expected response:

{
  "status": "OK",
  "responseDetails": [{ "code": 0, "subCode": 0 }]
}

If you receive a connection error, confirm with Green Dot that your outbound IP has been added to the allowlist.


Step 2 - Implement Token Acquisition

Implement a token service that acquires, caches, and refreshes your Azure AD Bearer token. The token is valid for ~3599 seconds - cache it and refresh proactively ~60–120 seconds before expiry.

POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={YOUR_CLIENT_ID}
&client_secret={YOUR_CLIENT_SECRET}
&scope=api://{AUDIENCE_ID}/.default

Token caching pseudocode:

function getToken():
    if cachedToken exists AND (expiresAt - now) > 90 seconds:
        return cachedToken
    response = fetchNewToken()
    cachedToken = response.access_token
    expiresAt = now + response.expires_in
    return cachedToken

See Authentication for full details and environment-specific AUDIENCE_ID values.


Step 3 - Implement PII Encryption

All customer PII must be encrypted before it reaches the API. Green Dot uses EC_v1 (ECIES — Elliptic Curve Integrated Encryption Scheme).

High-level process:

  1. Build the PII JSON object (name, address, SSN, DOB, email, phone).
  2. Serialize it as a UTF-8 string.
  3. Encrypt using Green Dot's EC public key (provided per environment).
  4. Base64-encode the ciphertext → this becomes encryptedUserData.data.
  5. Capture the ephemeral public key and public key hash generated during encryption → these go into encryptedUserData.ephemeralPublicKey and encryptedUserData.publicKeyHash.

The encrypted block in your request will look like:

"encryptedUserData": {
  "version": "EC_v1",
  "ephemeralPublicKey": "{ephemeral-key-from-encryption}",
  "publicKeyHash": "{hash-of-gd-public-key}",
  "data": "{base64-encoded-ciphertext}"
}

Contact your Green Dot integration contact for EC public keys per environment and to confirm the expected encryption library or implementation approach.


Step 4 - Build the Enrollment Request

Assemble the full enrollment request body. The PII goes inside the encrypted block; all other fields are plaintext.

Required flags - these must always be set exactly as shown:

"requestPhysicalCardFlag": false,
"executeKycFlag": true

Minimum required request structure:

{
  "user": {
    "encryptedUserData": {
      "version": "EC_v1",
      "ephemeralPublicKey": "...",
      "publicKeyHash": "...",
      "data": "..."
    }
  },
  "account": {
    "productCode": "{PRODUCT_CODE}",
    "productMaterialType": "{PRODUCT_MATERIAL_TYPE_VIRTUAL}",
    "currency": "USD",
    "termsAcceptances": [
      {
        "termsIdentifier": "daa",
        "termsAcceptanceFlag": true,
        "termsAcceptanceDateTime": "2024-04-15T10:30:00.000Z"
      }
      // ... all required terms for your product
    ],
    "fraudData": {
      "IpAddress": "{customer_ip_address}"
    }
  },
  "requestPhysicalCardFlag": false,
  "executeKycFlag": true
}

Common field validation errors to build against:

MistakeError
phone.type is not "mobile"400 / 603
termsAcceptanceDateTime missing fractional seconds or Z400 / 720
termsAcceptanceDateTime more than 720 hours ago400 / 620
ssnSuffix is not an empty string400 / 630
addressLine1 is a PO Box400 / 1042 / 31007
ZIP doesn't match state400 / 1042 / 31006
+ in email address (PROD only)400 / 750
requestPhysicalCardFlag is true400 / 101
executeKycFlag is false400 / 101

Step 5 - Call the Enrollment API and Handle the Response

POST {BASE_URL}/programs/{PROGRAM_CODE}/enrollment
Authorization: Bearer {access_token}
X-GD-RequestId: {fresh-uuid-per-request}
Content-Type: application/json

{request body}

Always check responseDetails[0].code - HTTP 201 is not unconditional success.

response = POST /enrollment

if response.http_status == 201 AND responseDetails[0].code == 0:
    // SUCCESS
    persist accountIdentifier
    persist paymentInstrumentIdentifier
    persist accountNumber + routingNumber (for IRS submission)
    store encryptedPrivatePaymentInstrumentData (decrypt with your private key for display)

else if response.http_status == 201 AND responseDetails[0].code == 2:
    // KYC / OFAC HARD DECLINE — permanent, do not retry
    subCode 11 = KYC hard decline
    subCode 31 = OFAC hard decline
    subCode 33 = KYC + OFAC both failed
    // Green Dot handles customer-facing communication

else if response.http_status == 200 AND responseDetails[0].code == 2:
    // ACCOUNT LIMIT or TERMS issue
    subCode 55 = required terms not accepted
    subCode 60 = active account limit by SSN
    subCode 61 = lifetime account limit
    // Do not retry

else if response.http_status == 400:
    // VALIDATION ERROR — fix the request, do not retry as-is
    log responseDetails[0].code + subCode for diagnosis

else if response.http_status == 503:
    // TRANSIENT — retry with exponential backoff

Step 6 - Submit Account & Routing Number to the IRS

After a successful enrollment, use the directDepositInformation from the response to submit the refund destination to the IRS (e.g., via Form 8888 or equivalent).

"directDepositInformation": {
  "accountNumber": "15101332927074",
  "routingNumber": "124303162"
}

This step is entirely within the partner's workflow - there is no Green Dot API call for it.


Step 7 - Implement the Order Card Trigger

When your business trigger fires (e.g., IRS acceptance confirmed, refund deposited), call the Order Card API using the identifiers stored from enrollment.

PUT {BASE_URL}/programs/{PROGRAM_CODE}/lifecycleEvent
Authorization: Bearer {access_token}
X-GD-RequestId: {fresh-uuid-per-request}
Content-Type: application/json

{
  "accountIdentifier": "{stored accountIdentifier}",
  "paymentInstrumentIdentifier": "{stored paymentInstrumentIdentifier}",
  "lifeCycleEventType": "replacement",
  "productMaterialType": "{PRODUCT_MATERIAL_TYPE_EMV}",
  "replaceReason": "initialPhysicalCard"
}

All Order Card errors return HTTP 200 with a non-zero code. Always read responseDetails[0].code.

response = PUT /lifecycleEvent

if responseDetails[0].code == 0:
    // SUCCESS — physical card ordered, no order ID returned

else if responseDetails[0].code == 4:
    subCode 105 = account closed — do not retry
    subCode 106 = account locked — do not retry
    subCode 300 = card lost/stolen — do not retry
    subCode 303 = stale paymentInstrumentIdentifier — retrieve current identifier and retry
    subCode 308 = duplicate within 10 days — already succeeded, do not retry
    subCode 310 = physical card already exists — do not retry
    subCode 323 = invalid mailing address — do not retry

else if responseDetails[0].code == 600:
    // Downstream service unavailable — retry with backoff

Step 8 - Test End-to-End

Before requesting Production access:

  1. Call /healthcheck and confirm all services return "status": "OK".
  2. Submit a complete enrollment with valid synthetic test data and confirm a 201 / code: 0 response.
  3. Confirm you are persisting accountIdentifier, paymentInstrumentIdentifier, and directDepositInformation.
  4. Submit an Order Card call using the identifiers from step 2 and confirm a 200 / code: 0 response.
  5. Test key error scenarios: duplicate email (101/192), invalid ZIP (1042/31006), missing terms (2/55), and downstream unavailable (503/600/0).
  6. Confirm your token caching and refresh logic works correctly across a multi-hour session.

Key Rules Summary

RuleDetail
Always set requestPhysicalCardFlag: falsePhysical card ordering is a separate call
Always set executeKycFlag: trueKYC cannot be skipped
Always check responseDetails[0].codeHTTP status alone is insufficient
Never reuse X-GD-RequestId across distinct requestsGenerate a fresh UUID per request
Cache your Bearer token~1 hour validity; refresh proactively
Set HTTP timeout to ≥ 35 secondsEnrollment runs synchronous KYC
Persist accountIdentifier and paymentInstrumentIdentifierBoth required for Order Card
initialPhysicalCard is one-time per accountOrder Card with this reason can only succeed once

Did this page help you?