{"templateId":"markdown","versions":[{"version":"1.0","label":"1.0","link":"/omni-api/1.0/overview/webhooks/processing-results","default":false,"active":false,"folderId":"6bec560c"},{"version":"1.1","label":"1.1","link":"/omni-api/1.1/overview/webhooks/processing-results","default":false,"active":false,"folderId":"6bec560c"},{"version":"1.2","label":"1.2","link":"/omni-api/overview/webhooks/processing-results","default":true,"active":true,"folderId":"6bec560c"}],"sharedDataIds":{"sidebar":"sidebar-omni-api/@1.0/overview/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"File upload processing-result webhook"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"file-upload-processing-result-webhook","__idx":0},"children":["File upload processing-result webhook"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Version 1.2 uses the signed ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FileUploadProcessingResult"]}," event for native and Plaid automated files. It replaces the schema-only event in this public contract. Production sends a terminal file result after validation failure or ingestion completion/failure. Successful sandbox validation reports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validation_only"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validationStatus=passed"]},"; failed validation reports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validationStatus=failed"]},". A passing validation result in production is not the terminal ingestion result."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"envelope","__idx":1},"children":["Envelope"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"webhookId\": \"evt_77777777-7777-4777-8777-777777777777\",\n  \"type\": \"FileUploadProcessingResult\",\n  \"data\": {\n    \"fileId\": \"fu_77777777-7777-4777-8777-777777777777\",\n    \"filename\": \"batch-2026-10-01-001.csv\",\n    \"content\": \"percents-txn-stream\",\n    \"receivedAt\": \"2026-10-01T12:01:00.000Z\",\n    \"validatedAt\": \"2026-10-01T12:02:00.000Z\",\n    \"processingStartedAt\": \"2026-10-01T12:02:00.000Z\",\n    \"processingFinishedAt\": \"2026-10-01T12:03:00.000Z\",\n    \"validationStatus\": \"passed\",\n    \"processingStatus\": \"completed\",\n    \"counts\": {\"detectedRows\": 100, \"ingestedRows\": 100, \"duplicateRows\": 0, \"rejectedRows\": 0},\n    \"errorMetadata\": null,\n    \"errorUuid\": null\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The result fields have the same meaning as the corresponding ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/omni-api/overview/file-uploads/status"},"children":["file-status fields"]},". The webhook omits reservation-only status fields such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createdAt"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["uploadExpiresAt"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fileType"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["uploadStatus"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["processingMode"]},". Counts may be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," when unavailable; timestamp fields may be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," when a stage has not occurred. Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fileId"]}," to reconcile the reservation."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validation rejects the whole native or Plaid file before ingestion if its contract is invalid. Plaid reference errors can include ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["missingAccountIds"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["unknownPercentsMerchantIds"]}," with a required ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["message"]},". ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ingestedRows"]}," counts persisted transaction facts, including transactions rejected by reward eligibility rules. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["duplicateRows"]}," counts skipped known duplicates. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["rejectedRows"]}," counts file-validation or technical ingestion failures, excluding reward/business rejection. These counts describe file handling, not transaction approvals. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["processingStartedAt"]}," records actual ingestion start and remains ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," when ingestion never begins. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["processingFinishedAt"]}," records terminal file handling, including validation failure or successful sandbox validation; it does not imply ingestion occurred. Rewards, invoices, and final individual transaction determinations are outside this contract."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"receiver-behavior","__idx":2},"children":["Receiver behavior"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Retain the raw request body and verify ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/omni-api/overview/webhooks/signatures"},"children":["X-Percents-Signature"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Deduplicate deliveries by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["webhookId"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Durably record the result and return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2xx"]}," quickly."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For failed files, inspect ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["errorMetadata"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["errorUuid"]},", correct the source, and use a new filename for resubmission."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Percents sends file results to your entity's configured webhook destination using its signing key. If no destination is configured, file processing still completes and the result remains available for delivery after a destination is configured. Percents bounds each delivery attempt to ten seconds and retries unsuccessful delivery with increasing delays. A delivery failure does not change the file-processing result. Receivers must be idempotent because the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["webhookId"]}," can be delivered more than once. Use the status endpoint to reconcile a delayed or missing webhook."]}]},"headings":[{"value":"File upload processing-result webhook","id":"file-upload-processing-result-webhook","depth":1},{"value":"Envelope","id":"envelope","depth":2},{"value":"Receiver behavior","id":"receiver-behavior","depth":2}],"frontmatter":{"seo":{"title":"File upload processing-result webhook"}},"lastModified":"2026-10-02T20:53:32.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/omni-api/overview/webhooks/processing-results","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}