# Outcome API Tutorial
source: https://developer.mastercard.com/ethoca-alerts-for-merchants/documentation/tutorials-and-guides/outcome-api-tutorial/index.md

## Submit Investigation Outcomes with the Ethoca Alerts for Merchants Outcome API {#submit-investigation-outcomes-with-the-ethoca-alerts-for-merchants-outcome-api}

This tutorial demonstrates how to submit investigation outcomes after processing alerts from either the Pull or Push API.

*** ** * ** ***

## Overview {#overview}

After receiving and acknowledging an alert (via Pull or Push), you investigate the transaction and submit an outcome back to Ethoca. This outcome is transmitted to the card issuer to help prevent chargebacks.

**Key points:**

* Submit outcomes within **24 hours** of alert receipt (best practice)
* You can submit up to **25 outcomes** per request
* Outcomes are immutable; the first final outcome you submit is what the issuer receives
* Different outcome codes apply to Confirmed Fraud vs. Customer Dispute alerts

*** ** * ** ***

## Outcome Codes Reference {#outcome-codes-reference}

### For CUSTOMERDISPUTE Alerts {#for-customerdispute-alerts}

Use one of these outcome codes:

|              Code              |                Description                 |                    Example Scenario                     |
|--------------------------------|--------------------------------------------|---------------------------------------------------------|
| `RESOLVED`                     | Dispute resolved, no refund needed         | Merchant contacted customer; transaction was authorized |
| `RESOLVED_PREVIOUSLY_REFUNDED` | Dispute resolved with previous refund      | Refund issued before dispute raised                     |
| `UNRESOLVED_DISPUTE`           | Unable to resolve                          | Insufficient evidence to dispute the chargeback         |
| `NOT_FOUND`                    | Alert received but no matching transaction | Declined or never attempted transaction                 |
| `OTHER`                        | Outcome doesn't fit standard codes         | Custom resolution with comments required                |

### For CONFIRMEDFRAUD Alerts {#for-confirmedfraud-alerts}

Use one of these outcome codes:

|          Code          |                   Description                    |                 Example Scenario                 |
|------------------------|--------------------------------------------------|--------------------------------------------------|
| `STOPPED`              | Transaction successfully stopped/blocked         | Fraud detected before payment cleared            |
| `PARTIALLY_STOPPED`    | Partial fraud amount stopped                     | Multiple transactions; only some blocked         |
| `PREVIOUSLY_CANCELLED` | Transaction already cancelled before fraud alert | Cancel request arrived before fraud notification |
| `MISSED`               | Transaction completed before action taken        | Unable to stop transaction (already settled)     |
| `NOT_FOUND`            | Alert received but no matching transaction       | Transaction declined or never attempted          |
| `ACCOUNT_SUSPENDED`    | Fraudster's account suspended                    | Account security compromised; account closed     |
| `OTHER`                | Outcome doesn't fit standard codes               | Custom fraud resolution with comments            |

*** ** * ** ***

## Step 1: Prepare Outcome Data {#step-1-prepare-outcome-data}

Gather investigation results and determine the appropriate outcome code:

```java
public class OutcomePreparation {
  
  public Outcome investigateAndPrepareOutcome(Alert alert) {
    // 1. Retrieve transaction from your system
    Transaction txn = txnRepository.findByRef(alert.getTransactionRef());
    
    // 2. Investigate fraud indicators
    boolean isFraudulent = checkFraudIndicators(txn);
    
    // 3. Determine outcome
    String outcomeCode;
    if (alert.getAlertType().equals("CONFIRMEDFRAUD")) {
      outcomeCode = isFraudulent ? "STOPPED" : "MISSED";
    } else {
      outcomeCode = "RESOLVED";
    }
    
    // 4. Gather additional info
    String refundStatus = txn.isRefunded() ? "REFUNDED" : "NOT_REFUNDED";
    String refundAmount = txn.getRefundAmount();
    
    // 5. Prepare outcome object
    Outcome outcome = new Outcome();
    outcome.setAlertId(alert.getAlertId());
    outcome.setAlertType(alert.getAlertType());
    outcome.setOutcomeCode(outcomeCode);
    outcome.setRefundStatus(refundStatus);
    outcome.setRefundAmount(refundAmount);
    outcome.setComments("Fraud detected via velocity checking and geolocation mismatch");
    
    return outcome;
  }
}
```

*** ** * ** ***

## Step 2: Submit a Single Outcome {#step-2-submit-a-single-outcome}

### Request {#request}

Submit one or more outcomes:

```bash
curl --request POST \
  --url 'https://sandbox.api.ethocaweb.com/ethoca/alerts/merchants/outcomes' \
  --header 'Authorization: OAuth oauth_consumer_key="YOUR_CONSUMER_KEY", oauth_signature_method="RSA-SHA256", oauth_signature="...", oauth_timestamp="...", oauth_nonce="..."' \
  --header 'Content-Type: application/json' \
  --data '{
    "outcomes": [
      {
        "alertId": "ALERT_ID_001",
        "alertType": "CUSTOMERDISPUTE",
        "outcomeCodes": [
          {
            "codeType": "INVESTIGATION_RESULT",
            "code": "RESOLVED"
          }
        ],
        "refundInformation": {
          "refundStatus": "NOT_REFUNDED",
          "refundType": "REFUND",
          "refundAmount": "0.00"
        },
        "comments": "Transaction verified as legitimate. Customer confirmed authorization."
      }
    ]
  }'
```

#### Request Fields {#request-fields}

|              Field               |  Type  | Required |                         Description                         |
|----------------------------------|--------|----------|-------------------------------------------------------------|
| `alertId`                        | String | Yes      | Alert ID from the alert payload                             |
| `alertType`                      | String | Yes      | Either `CUSTOMERDISPUTE` or `CONFIRMEDFRAUD`                |
| `outcomeCodes[].codeType`        | String | Yes      | Always use `INVESTIGATION_RESULT`                           |
| `outcomeCodes[].code`            | String | Yes      | Outcome code (see reference above)                          |
| `refundInformation.refundStatus` | String | Yes      | Either `REFUNDED` or `NOT_REFUNDED`                         |
| `refundInformation.refundType`   | String | No       | `REFUND`, `VOUCHER`, `POINTS`, or `GIFT_CARD` (if refunded) |
| `refundInformation.refundAmount` | String | No       | Refund amount (for example, "150.00") if refunded           |
| `comments`                       | String | No       | Additional investigation notes (max 500 chars)              |

### Response {#response}

```json
{
  "outcomes": [
    {
      "alertId": "ALERT_ID_001",
      "status": "SUCCESS"
    }
  ]
}
```

*** ** * ** ***

## Step 3: Submit Batch Outcomes {#step-3-submit-batch-outcomes}

Submit multiple outcomes in a single request (up to 25):

### Request {#request-1}

```bash
curl --request POST \
  --url 'https://sandbox.api.ethocaweb.com/ethoca/alerts/merchants/outcomes' \
  --header 'Authorization: OAuth oauth_consumer_key="YOUR_CONSUMER_KEY", oauth_signature_method="RSA-SHA256", oauth_signature="...", oauth_timestamp="...", oauth_nonce="..."' \
  --header 'Content-Type: application/json' \
  --data '{
    "outcomes": [
      {
        "alertId": "ALERT_ID_001",
        "alertType": "CUSTOMERDISPUTE",
        "outcomeCodes": [
          {
            "codeType": "INVESTIGATION_RESULT",
            "code": "RESOLVED"
          }
        ],
        "refundInformation": {
          "refundStatus": "NOT_REFUNDED",
          "refundType": "REFUND",
          "refundAmount": "0.00"
        },
        "comments": "Transaction verified as legitimate."
      },
      {
        "alertId": "ALERT_ID_002",
        "alertType": "CONFIRMEDFRAUD",
        "outcomeCodes": [
          {
            "codeType": "INVESTIGATION_RESULT",
            "code": "STOPPED"
          }
        ],
        "refundInformation": {
          "refundStatus": "REFUNDED",
          "refundType": "REFUND",
          "refundAmount": "250.00"
        },
        "comments": "Fraudulent transaction; refund issued and account suspended."
      }
    ]
  }'
```

### Response {#response-1}

```json
{
  "outcomes": [
    {
      "alertId": "ALERT_ID_001",
      "status": "SUCCESS"
    },
    {
      "alertId": "ALERT_ID_002",
      "status": "SUCCESS"
    }
  ]
}
```

*** ** * ** ***

## Step 4: Handle Batch Failures {#step-4-handle-batch-failures}

When submitting multiple outcomes, some may succeed while others fail:

### Response with Mixed Results {#response-with-mixed-results}

```json
{
  "outcomes": [
    {
      "alertId": "ALERT_ID_001",
      "status": "SUCCESS"
    },
    {
      "alertId": "ALERT_ID_002",
      "status": "FAILED",
      "error": {
        "Source": "API",
        "ReasonCode": "VALIDATION_FAILURE",
        "Description": "Invalid outcome code for alert type CONFIRMEDFRAUD",
        "Recoverable": false
      }
    }
  ]
}
```

**Resolution:** Fix the invalid outcome code (for example, use `STOPPED` for `CONFIRMEDFRAUD` instead of `RESOLVED`) and resubmit.

*** ** * ** ***

## Common Patterns {#common-patterns}

### Batch Processing with Retry Logic {#batch-processing-with-retry-logic}

```java
public void submitOutcomesWithRetry(List<Outcome> outcomes) {
  int maxRetries = 3;
  int retryCount = 0;
  List<Outcome> failedOutcomes = outcomes;
  
  while (!failedOutcomes.isEmpty() && retryCount < maxRetries) {
    OutcomeResponse response = submitOutcomes(failedOutcomes);
    
    // Separate successes and failures
    List<Outcome> nextBatch = new ArrayList<>();
    for (OutcomeResult result : response.getOutcomes()) {
      if (!result.isSuccess() && result.isRecoverable()) {
        nextBatch.add(findOutcomeById(result.getAlertId()));
      } else if (!result.isSuccess()) {
        logger.error("Permanent failure for alert {}", result.getAlertId());
      }
    }
    
    failedOutcomes = nextBatch;
    retryCount++;
    
    if (!failedOutcomes.isEmpty() && retryCount < maxRetries) {
      // Exponential backoff
      long backoffMs = 1000 * (long) Math.pow(2, retryCount);
      Thread.sleep(backoffMs);
    }
  }
}
```

### 24-Hour SLA Monitoring {#24-hour-sla-monitoring}

```java
@Scheduled(fixedDelay = 3600000) // Every hour
public void checkOutcomeSLA() {
  List<Alert> oldAlerts = alertRepository.findOlderThan(Duration.ofHours(20));
  
  for (Alert alert : oldAlerts) {
    if (!outcomeRepository.existsByAlertId(alert.getAlertId())) {
      // Alert is approaching 24-hour SLA without outcome
      logger.warn("Alert {} approaching 24-hour SLA deadline", alert.getAlertId());
      alertManagement.escalateToManual(alert);
    }
  }
}
```

### Handling Immutable Outcomes {#handling-immutable-outcomes}

Once an outcome is submitted, it cannot be updated. If you need to change it, contact support:

```java
public void submitOutcomeWithValidation(Outcome outcome) {
  // Validate before submission
  if (!isValidOutcome(outcome)) {
    throw new ValidationException("Invalid outcome; check outcome code");
  }
  
  // Submit
  OutcomeResponse response = submitOutcome(outcome);
  
  if (response.isSuccess()) {
    // Outcome is now immutable
    logger.info("Outcome submitted for alert {}; no changes possible", 
      outcome.getAlertId());
  }
}
```

*** ** * ** ***

## Example: Complete Workflow {#example-complete-workflow}

```java
public class OutcomeWorkflow {
  
  public void processAlert(Alert alert) throws Exception {
    // 1. Receive alert (from Pull or Push)
    logger.info("Received alert {}", alert.getAlertId());
    
    // 2. Queue for investigation
    investigationQueue.enqueue(alert);
  }
  
  @Async
  public void investigateAndSubmitOutcome(Alert alert) throws Exception {
    // 3. Investigate (may take hours)
    InvestigationResult result = investigateTransaction(alert);
    
    // 4. Determine outcome
    Outcome outcome = determineOutcome(alert, result);
    
    // 5. Submit outcome
    OutcomeResponse response = submitOutcome(outcome);
    
    if (response.isSuccess()) {
      logger.info("Outcome submitted successfully for alert {}", alert.getAlertId());
      auditLog.record(alert.getAlertId(), outcome, "SUCCESS");
    } else {
      logger.error("Failed to submit outcome for alert {}", alert.getAlertId());
      deadLetterQueue.send(outcome);
    }
  }
  
  private InvestigationResult investigateTransaction(Alert alert) {
    Transaction txn = txnRepository.findByRef(alert.getTransactionRef());
    
    // Check fraud indicators
    boolean velocityAnomalies = checkVelocityPattern(txn);
    boolean geolocationMismatch = checkGeolocation(txn);
    boolean deviceFingerprintMismatch = checkDeviceFingerprint(txn);
    
    // Determine fraud likelihood
    boolean likelyFraud = velocityAnomalies && (geolocationMismatch || deviceFingerprintMismatch);
    
    return new InvestigationResult(likelyFraud, "Multiple fraud indicators detected");
  }
  
  private Outcome determineOutcome(Alert alert, InvestigationResult result) {
    Outcome outcome = new Outcome();
    outcome.setAlertId(alert.getAlertId());
    outcome.setAlertType(alert.getAlertType());
    
    if (alert.getAlertType().equals("CONFIRMEDFRAUD")) {
      outcome.setOutcomeCode(result.isFraud() ? "STOPPED" : "MISSED");
    } else {
      outcome.setOutcomeCode("RESOLVED");
    }
    
    outcome.setRefundStatus("NOT_REFUNDED");
    outcome.setComments(result.getNotes());
    
    return outcome;
  }
}
```

*** ** * ** ***

## Next Steps {#next-steps}

* Review [Testing](https://developer.mastercard.com/ethoca-alerts-for-merchants/documentation/tutorials-and-guides/testing/index.md) for comprehensive test scenarios
* Check [Codes \& Formats](https://developer.mastercard.com/ethoca-alerts-for-merchants/documentation/tutorials-and-guides/code-and-formats/index.md) for error code reference
* See [Data Explanations](https://static.developer.mastercard.com/content/ethoca-alerts-for-merchants/documentation/tutorials-and-guides/outcome-api-tutorial/data-explanations.md) for field reference
* Visit [API Reference](https://developer.mastercard.com/ethoca-alerts-for-merchants/documentation/tutorials-and-guides/api-reference/index.md) for complete endpoint documentation
