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:
- Build the PII JSON object (name, address, SSN, DOB, email, phone).
- Serialize it as a UTF-8 string.
- Encrypt using Green Dot's EC public key (provided per environment).
- Base64-encode the ciphertext → this becomes
encryptedUserData.data. - Capture the ephemeral public key and public key hash generated during encryption → these go into
encryptedUserData.ephemeralPublicKeyandencryptedUserData.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": trueMinimum 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:
| Mistake | Error |
|---|---|
phone.type is not "mobile" | 400 / 603 |
termsAcceptanceDateTime missing fractional seconds or Z | 400 / 720 |
termsAcceptanceDateTime more than 720 hours ago | 400 / 620 |
ssnSuffix is not an empty string | 400 / 630 |
addressLine1 is a PO Box | 400 / 1042 / 31007 |
| ZIP doesn't match state | 400 / 1042 / 31006 |
+ in email address (PROD only) | 400 / 750 |
requestPhysicalCardFlag is true | 400 / 101 |
executeKycFlag is false | 400 / 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:
- Call
/healthcheckand confirm all services return"status": "OK". - Submit a complete enrollment with valid synthetic test data and confirm a
201 / code: 0response. - Confirm you are persisting
accountIdentifier,paymentInstrumentIdentifier, anddirectDepositInformation. - Submit an Order Card call using the identifiers from step 2 and confirm a
200 / code: 0response. - Test key error scenarios: duplicate email (
101/192), invalid ZIP (1042/31006), missing terms (2/55), and downstream unavailable (503/600/0). - Confirm your token caching and refresh logic works correctly across a multi-hour session.
Key Rules Summary
| Rule | Detail |
|---|---|
Always set requestPhysicalCardFlag: false | Physical card ordering is a separate call |
Always set executeKycFlag: true | KYC cannot be skipped |
Always check responseDetails[0].code | HTTP status alone is insufficient |
Never reuse X-GD-RequestId across distinct requests | Generate a fresh UUID per request |
| Cache your Bearer token | ~1 hour validity; refresh proactively |
| Set HTTP timeout to ≥ 35 seconds | Enrollment runs synchronous KYC |
Persist accountIdentifier and paymentInstrumentIdentifier | Both required for Order Card |
initialPhysicalCard is one-time per account | Order Card with this reason can only succeed once |
Updated 6 days ago
