Historical Transaction Uploads

Overview

Kard supports bulk file-based ingestion of historical transactions for issuers onboarding new users or backfilling historical data after enrollment. Instead of submitting historical transactions one user at a time via the per-user upload API, you can upload a single file containing historical transactions for many users at once. Kard processes the file asynchronously and notifies you via webhook when processing is complete.

This guide covers how to request a presigned upload URL, prepare and upload your transaction file, and interpret the webhook notification you’ll receive once processing is complete.

Migrating from the per-user upload API. The multi-part Historical Transactions Upload API (Create Upload → Add Upload Part → Update Upload) is deprecated in favor of the bulk flow described in this guide. Existing integrations will continue to work until end of year 2027, but new integrations should use the bulk flow. The bulk flow supports millions of transactions per upload (vs. 500 per request) and a single file across many users (vs. one upload per user).

Key Concepts

Transaction Types

Each line in the file describes a single historical transaction. Two transaction types are supported — pick the one that matches your data source.

TypeWhen to use
historicalTransactionStandard card issuers with card-linked transaction data, including merchant and card-level metadata.
coreHistoricalTransactionCore banking systems that lack detailed card or merchant metadata.

File Format

Transaction files use JSONL format — one JSON object per line, with no wrapping array or commas between lines. Files can be plain text or gzip-compressed; Kard auto-detects the format. A single upload supports files up to 5 GB.

Processing Model

The flow is asynchronous and mirrors Kard’s daily Bulk Transaction Upload API:

  1. Request an upload URL. Call the file uploads endpoint to receive a presigned S3 URL.
  2. Upload the file. Send your JSONL file directly to S3 using the presigned URL.
  3. Kard processes the file. Each line is validated and accepted records are forwarded to historical transaction storage.
  4. Receive a completion webhook. Once processing finishes, Kard sends a fileProcessingResult notification with a download link to a results file containing any per-line errors.

Implementation

Step 1: Request a Presigned Upload URL

Call the file uploads endpoint to receive one presigned S3 URL per file you intend to upload. You may request up to 10 URLs in a single call — one per file.

POST /v2/issuers/{{organizationId}}/transactions/uploads

Request body:

1{
2 "data": [
3 {
4 "type": "historicalTransactionsFile",
5 "attributes": {
6 "filename": "historical_transactions_2024-10-15.jsonl"
7 }
8 }
9 ]
10}
FieldTypeRequiredDescription
dataarrayYesList of file upload sessions to create. Maximum 10 items per request.
data[].typestringYesMust be historicalTransactionsFile.
data[].attributes.filenamestringYesName of the file you intend to upload, including extension.

Response (201 OK):

1{
2 "data": [
3 {
4 "type": "historicalTransactionsFile",
5 "id": "2Rk3pHqLm9vNwXs1TyBf0Jc4dE",
6 "attributes": {
7 "url": "https://s3.amazonaws.com/kard-uploads/...",
8 "expiresIn": 900
9 }
10 }
11 ]
12}
FieldTypeDescription
data[].idstringUnique identifier for the upload session. Save this — it is embedded in the S3 object key and can be used to correlate your upload with the file in storage.
data[].attributes.urlstringPresigned S3 PUT URL. Use it exactly as returned.
data[].attributes.expiresInintegerNumber of seconds until the presigned URL expires (900 / 15 minutes).

Presigned URLs expire after 15 minutes. If your URL expires before you complete the upload, simply request a new one.

Step 2: Prepare Your Transaction File

Each line in the file is a single JSON object representing one transaction. Choose the transaction type that matches your data source.

type: historicalTransaction

Use this type if you have card-linked transaction data with merchant and card metadata.

Example line (formatted across multiple lines for readability — in your .jsonl file each transaction must be on a single line):

1{
2 "id": "309rjfoincor3icno3rind093cdow3jciwjdwcm",
3 "type": "historicalTransaction",
4 "attributes": {
5 "userId": "6FHt5b6Fnp0qdomMEy5AN6PXcSJIeX69",
6 "transactionId": "2467de37-cbdc-416d-a359-75de87bfffb0",
7 "status": "APPROVED",
8 "amount": 1000,
9 "subtotal": 800,
10 "currency": "USD",
11 "direction": "DEBIT",
12 "paymentType": "CARD",
13 "description": "ADVANCEAUTO",
14 "description2": "ADVANCEAUTO",
15 "mcc": "1234",
16 "cardBIN": "123456",
17 "cardLastFour": "4321",
18 "cardNetwork": "VISA",
19 "authorizationDate": "2021-07-02T17:47:06Z",
20 "authorizationCode": "123456",
21 "retrievalReferenceNumber": "100804333919",
22 "acquirerReferenceNumber": "1234567890123456789012345678",
23 "systemTraceAuditNumber": "333828",
24 "merchant": {
25 "id": "12345678901234567",
26 "name": "ADVANCEAUTO",
27 "addrStreet": "125 Main St",
28 "addrCity": "Philadelphia",
29 "addrState": "PA",
30 "addrZipcode": "19147",
31 "addrCountry": "United States",
32 "latitude": "37.9419429",
33 "longitude": "-73.1446869",
34 "storeId": "12345"
35 }
36 }
37}

Field reference:

FieldTypeRequiredDescription
typestringYesMust be historicalTransaction.
idstringYesUnique identifier for this transaction record.
attributes.userIdstringYesUser identifier as known to Kard.
attributes.transactionIdstringYesUnique transaction identifier from your system.
attributes.amountintegerYesTransaction amount in cents.
attributes.subtotalintegerNoPre-tax/tip amount in cents.
attributes.currencystringYesISO 4217 currency code.
attributes.statusstringYesTransaction status (e.g. APPROVED, SETTLED, REVERSED).
attributes.directionstringYesDEBIT or CREDIT.
attributes.paymentTypestringNoPayment method (e.g. CARD).
attributes.descriptionstringYesRaw transaction description.
attributes.description2stringNoSecondary description from the network.
attributes.mccstringNoMerchant category code.
attributes.cardBINstringYesFirst 6 digits of the card.
attributes.cardLastFourstringYesLast 4 digits of the card.
attributes.cardNetworkstringNoE.g. VISA, MASTERCARD.
attributes.authorizationDatestringYesISO 8601 datetime of the authorization.
attributes.merchantobjectYesMerchant detail object (see below).
attributes.authorizationCodestringNoAuthorization code from the issuer.
attributes.retrievalReferenceNumberstringNoRetrieval Reference Number (RRN).
attributes.acquirerReferenceNumberstringNoAcquirer Reference Number (ARN).
attributes.systemTraceAuditNumberstringNoSystem Trace Audit Number (STAN).

merchant object:

FieldTypeDescription
idstringMerchant identifier.
namestringMerchant name.
addrStreetstringStreet address.
addrCitystringCity.
addrStatestringState.
addrZipcodestringZip / postal code.
addrCountrystringCountry.
latitudestringLatitude.
longitudestringLongitude.
storeIdstringStore identifier.

type: coreHistoricalTransaction

Use this type if you operate on a core banking system that lacks card or merchant detail. This type mirrors the relationship between coreTransaction and transaction in the daily ingestion flow.

Example line:

1{
2 "id": "a1b2c3d4e5f6g7h8i9j0",
3 "type": "coreHistoricalTransaction",
4 "attributes": {
5 "userId": "6FHt5b6Fnp0qdomMEy5AN6PXcSJIeX69",
6 "transactionId": "2467de37-cbdc-416d-a359-75de87bfffb0",
7 "amount": 1000,
8 "currency": "USD",
9 "description": "ADVANCEAUTO",
10 "direction": "DEBIT",
11 "status": "SETTLED",
12 "settledDate": "2021-07-02T17:47:06Z",
13 "authorizationDate": "2021-07-02T17:47:06Z",
14 "financialInstitutionId": "First National Bank",
15 "cardLastFours": ["4321", "8765"]
16 }
17}

Field reference:

FieldTypeRequiredDescription
typestringYesMust be coreHistoricalTransaction.
idstringYesUnique identifier for this transaction record.
attributes.userIdstringYesUser identifier as known to Kard.
attributes.transactionIdstringYesUnique transaction identifier from your system.
attributes.amountintegerYesTransaction amount in cents.
attributes.currencystringYesISO 4217 currency code.
attributes.descriptionstringYesRaw transaction description from the core banking system.
attributes.directionstringYesDEBIT or CREDIT.
attributes.statusstringYesTransaction status. Always SETTLED for core banking transactions.
attributes.settledDatestringYesISO 8601 datetime of settlement.
attributes.authorizationDatestringYesISO 8601 datetime of the authorization.
attributes.financialInstitutionIdstringNoFinancial institution’s unique ID.
attributes.cardLastFoursarray<string>NoList of card last fours associated with this transaction.

Step 3: Upload the File

Once you have a presigned URL, upload your file using a standard HTTP PUT. The file is sent directly to Kard’s secure storage.

Plain text:

PUT {{presignedUrl}}
Content-Type: application/octet-stream
<binary file content>

Gzip-compressed:

PUT {{presignedUrl}}
Content-Type: application/gzip
<gzip-compressed file content>

A 201 response means the upload succeeded and Kard has begun processing the file.

The presigned URL accepts uploads up to 5 GB. For files with millions of rows, gzip compression typically reduces size by ~75%.

Step 4: Receive the Completion Webhook

Once Kard finishes processing your file, a single fileProcessingResult notification is sent to your configured webhook URL. The notification contains a presigned download URL pointing to a results file in Kard’s S3 bucket.

To configure webhook subscriptions, see the Create Subscription API.

Example payload:

1{
2 "data": {
3 "type": "fileProcessingResult",
4 "id": "c94a93a7-beb9-4e58-960c-2c812f849398",
5 "attributes": {
6 "fileName": "historical_transactions_2024-10-15.jsonl",
7 "sentAt": "2025-03-09T21:56:23Z",
8 "lastModified": "2025-03-19T21:56:23Z",
9 "downloadUrl": "https://s3.amazonaws.com/kard-results/xyz789/results.json?<signed-params>"
10 }
11 }
12}
FieldTypeDescription
data.typestringAlways fileProcessingResult.
data.idstringUnique identifier for the notification.
data.attributes.fileNamestringName of the file that was processed.
data.attributes.sentAtstringTimestamp when the file was originally uploaded.
data.attributes.lastModifiedstringTimestamp when the file was last modified.
data.attributes.downloadUrlstringPresigned URL to download the results file from S3.

The fileProcessingResult notification is only sent once ingestion has fully completed. If you do not receive it, treat that as an indication the file did not complete processing successfully.

Step 5: Download and Interpret the Results File

Use the downloadUrl from the notification to fetch the results file via a standard HTTP GET. The presigned URL expires after a set period — download promptly.

The results file lists any per-line errors encountered during ingestion. A successful ingestion with no errors will return a single success message with an empty errors array.

Example results file:

1{
2 "data": {
3 "type": "fileProcessingResult",
4 "id": "c94a93a7-beb9-4e58-960c-2c812f849398",
5 "attributes": {
6 "fileType": "historicalTransactionsFile",
7 "fileName": "historical_transactions_2024-10-15.jsonl",
8 "fileId": "xyz789"
9 }
10 },
11 "errors": [
12 {
13 "status": "400",
14 "title": "Invalid Request",
15 "detail": "Invalid request: .userId should be a string",
16 "id": "txn-123"
17 }
18 ]
19}
FieldTypeDescription
data.attributes.fileTypestringAlways historicalTransactionsFile.
data.attributes.fileNamestringName of the processed file.
data.attributes.fileIdstringIdentifier of the processed file.
errorsarrayList of all transaction-level errors encountered during ingestion. Empty if no errors occurred.
errors[].statusstringHTTP status code for the error.
errors[].titlestringShort description of the error type.
errors[].detailstringDetailed error message describing what went wrong. Will contain the JSONL transaction body that failed to process.
errors[].idstringOptional identifier of the transaction that failed.

If errors is empty, the file processed cleanly. Otherwise, use errors[].detail and errors[].id to correlate failures back to specific lines in your original file, then remediate and re-submit any failed transactions in a new file.

Idempotency. If a transaction is submitted more than once (e.g. across overlapping files or a retry), Kard treats the duplicate as a successful no-op. You do not need to track which transactions have already been sent.

Best Practices

  • Use gzip compression for large files. For files with millions of rows, compression significantly reduces upload time and bandwidth.
  • Use consistent user identifiers. The userId on each line must match the user identifier you use elsewhere in your Kard integration so transactions are attributed correctly.
  • Validate locally before uploading. Ensure each line is valid JSON and conforms to the schema for the type you’re sending. Invalid lines are reported in the results file but the rest of the file still processes.
  • Use the presigned URL exactly as returned. Do not modify the URL, query parameters, or method.
  • Request a new URL if yours expires. Presigned URLs are valid for 15 minutes. If your upload takes longer to prepare, simply request another one.