Event feed output files
An event feed picks up a file of activity from your SFTP server or S3 bucket, checks each row, and posts it as an event through the same rules as POST /events. Once every row has an outcome, the feed can write an output file back to the same server or bucket, listing what happened to each row: its status, the points it earned, the customer's balance afterwards, and why it failed.
You design the output in the Loom Loyalty Hub feed builder, on the feed's Output tab. Each environment sets its own output folder and file-name pattern.
Layout
- Format: CSV (comma-separated) or PSV (pipe-separated).
- Columns: in the order you choose, each with an optional heading. A column heading row is optional.
- Rows: every row of the input file, or only the rows that failed or conflicted. In failed-only mode, no file is written when nothing failed.
- Header and trailer records: the output can echo the input file's own header and footer records, or define its own (see Header and trailer records).
The output is written once the whole file has been processed. Loom Loyalty keeps its own copy, which you can download from the feed's run in the Hub or through GET /event-feeds/{feedId}/runs/{runId}/reply-file.
Column types
| Column | What it holds |
|---|---|
| Source column | A column of the input row, by name or position. Card numbers are redacted. |
| Line number | The row's line number in the input file. |
| Row id | The row's transaction id. |
| Status | The row's outcome (see Row statuses). |
| Points earned | The points the row earned in one program currency, after caps. |
| Points total | The points the row earned across all currencies. |
| Balance after | The customer's available balance in one program currency right after the row posted. |
| Error code | The row's error code from the list below. Blank on a row that posted. |
| Error description | The error in plain words. Blank on a row that posted. |
| Reason | The error code and description together, as CODE: description. Kept for existing feeds; use Error code and Error description for new ones. |
| Field | The input field an error names. Kept for existing feeds. |
How points and balances are counted
- Points count only what the row's own rules issued, after caps. Points issued by flows are not counted.
- Balances are the customer's available (spendable) points. Pending points are not included.
- A currency the customer has no account in reads
0. - A row that failed or was skipped earned
0points. Its balance column shows the customer's current balance if the row got as far as posting, and is blank if it failed its checks first, before a customer was reached. - A DUPLICATE row (its transaction id was already posted) repeats the points it earned when it first posted, with the customer's balance as of now. Nothing is posted again. Resending a file you lost the output for is safe.
When the output has any points or balance column, every row of the feed issues its points as it posts, whatever each rule's own timing setting says. This makes the numbers in the file final, and it makes large files take longer to process.
Formatting values
Each column, and each header and trailer field, can take a list of steps that run in order on its value.
| Step | Settings | What it does |
|---|---|---|
| Trim | — | Removes leading and trailing spaces. |
| Case | UPPER or LOWER | Changes letter case. |
| Format number | decimal places, decimal separator, grouping separator | Writes a number with fixed decimal places, for example 1234.5 as 1,234.50. |
| Implied decimals | places | Moves the decimal point right and drops it, for example 12.34 with 2 places becomes 1234. |
| Format date | pattern | Writes a date in a pattern, for example yyyyMMdd. |
| Pad | width, character, side | Pads the value to a fixed width, for example 500 padded to 9 with 0 on the left becomes 000000500. |
| Default | value | Uses a fixed value when the value is empty. |
| Lookup | table, default | Replaces the value with its entry in one of the feed's lookup tables. |
| Expression | CEL expression | Sets the value with a sandboxed CEL expression. |
An output expression can read the row's points and balances (maps from currency code to amount) and its status. balances only holds the currencies the output's balance columns name, so guard any other currency with has(balances.PTS).
Header and trailer records
A header or trailer record is one line made of fields, each with its own formatting steps:
| Field | What it holds |
|---|---|
| Constant | A fixed value, such as a record type of T. |
| Run date and time | When the output was written, in an optional date pattern. |
| Line count | How many rows the output lists, or only the rows with the chosen statuses. |
| Points total | The points all the file's rows earned in one currency. |
For example, a trailer of a T constant, a line count, and a points total padded to 9 digits writes T|3|000001150.
Row statuses
| Status | Meaning |
|---|---|
POSTED | The row posted as an event. |
SKIPPED | The feed's skip condition held, so the row was not posted. This is not a failure. |
FAILED | The row failed a check or could not post. See its error code. |
DUPLICATE | The transaction id was already posted with the same content. It was not posted again. |
CONFLICT | The transaction id was already posted with different content. It was not posted. |
Error codes
Match on the error code, not the description: codes are stable, and descriptions may be reworded.
Row errors
| Code | Meaning |
|---|---|
CARD_NUMBER_DETECTED | A value holds a full card number. Card numbers are not accepted. |
INVALID_DATE | A date couldn't be read or written with its pattern. |
INVALID_NUMBER | A value couldn't be read as a number. |
INVALID_VALUE | A value doesn't fit the event field's type. |
LOOKUP_NO_MATCH | A lookup found no entry for the value, and has no default. |
EXPRESSION_FAILED | An expression raised an error. |
EXPRESSION_LIMIT_EXCEEDED | An expression ran too long. |
SKIP_CONDITION_FAILED | The skip condition couldn't be evaluated. |
ROW_ID_MISSING | The row's transaction id is empty. |
ROW_ID_TOO_LONG | The row's transaction id is longer than 255 characters. |
EVENT_ID_INVALID | The feed reads the event id from the file, and this row's id is not a UUID. |
CUSTOMER_KEY_MISSING | The row's customer key is empty. |
CUSTOMER_NOT_FOUND | No customer has the row's customer key. |
CUSTOMER_AMBIGUOUS | More than one customer has the row's customer key. |
EVENT_DATE_TIME_INVALID | The row's event date is missing or isn't a valid date. |
EVENT_TOO_OLD | The row's event date is older than the feed's maximum age. |
EVENT_IN_FUTURE | The row's event date is more than 24 hours after the file arrived. |
REQUIRED_FIELD_MISSING | A required field is missing or blank. |
TYPE_MISMATCH | A value isn't of the type a check expects. |
VALUE_NOT_ALLOWED | A value isn't one of the allowed values. |
OUT_OF_RANGE | A number is outside the allowed range. |
LENGTH_OUT_OF_RANGE | A value is too short or too long. |
PATTERN_MISMATCH | A value doesn't match the required pattern. |
EXPRESSION_CHECK_FAILED | An expression check didn't pass. |
CONDITION_NOT_MET | A condition check didn't hold. |
EVENT_DATA_INVALID | The mapped event doesn't match the event type's data model. |
DUPLICATE_ROW | The transaction id was already posted, or appears earlier in the file, with the same content. |
ROW_CONFLICT | The transaction id was already posted, or appears earlier in the file, with different content. |
RULE_FAILED | The row posted, but a rule rejected it. The same transaction id can be sent again in a later file. |
PROCESSING_ERROR | The row posted, but its processing kept failing. Contact support with the run and line number. |
NOT_POSTED | The row passed its checks but was never posted, because the stored file changed during the run. |
EVALUATION_FAILED | The row couldn't be evaluated because of an unexpected error. Contact support with the run and line number. |
File errors
These fail the whole file. No row is posted, and the run reports the code instead of an output file.
| Code | Meaning |
|---|---|
COUNT_MISMATCH | The footer's record count differs from the file's rows. |
CONTROL_TOTAL_MISMATCH | The footer's control total differs from the sum of the rows' values. |
HEADER_RECORD_MISSING | The feed expects a header record, and the file has none. |
FOOTER_RECORD_MISSING | The feed expects a footer record, and the file has none. |
FOOTER_RECORD_INVALID | The footer's record count or control total isn't a number. |
INVALID_ENCODING | The file isn't valid in its encoding. |
UNTERMINATED_QUOTE | A quoted value is never closed. |
LIMIT_EXCEEDED | A row is too long, the file has too many rows, or processing took too long. |
Run-completed webhook
When a run finishes, the event_feed_run.completed webhook carries its line counts and, as an optional pointsEarned field, the points the run's rows earned per currency. See Webhooks.