Skip to main content
This guide helps you resolve issues when a CCTP attestation takes longer than expected to become available or when the attestation API returns unexpected responses.

Understanding attestation timing

After a successful burn transaction, Circle’s Attestation Service (Iris) must:
  1. Observe the burn event on the source blockchain
  2. Wait for sufficient block confirmations
  3. Sign the message and make the attestation available through the CCTP API
This process takes different amounts of time depending on the transfer type:
Transfer typeFinality thresholdTypical wait time
Fast Transfer≤ 1000Seconds to a few minutes
Standard Transfer≥ 2000Varies by blockchain (see Finality and Block Confirmations)

Why 404 responses are expected

The attestation API returns a 404 response until the attestation service has observed and processed your burn transaction. This is expected and does not indicate an error. The API returns 404 when:
  • The burn transaction hasn’t reached the required block confirmations
  • The attestation service hasn’t yet indexed the transaction
  • The transaction hash or domain ID is incorrect
Don’t treat 404 as a failure. Instead, implement polling with appropriate intervals.

Check attestation status

Query the GET /v2/messages endpoint to check the current status of your attestation. The following table explains the possible responses and what to do next:
ResponseMeaningAction
404Attestation not yet observedContinue polling until the attestation is available
{ "messages": [] }Transaction found but not processedContinue polling until the transaction is processed
{ "status": "pending" }Awaiting block confirmationsContinue polling until the block confirmations are reached
{ "status": "complete" }Attestation readyProceed to mint

Implement effective polling

Poll the attestation API at regular intervals without exceeding rate limits:
JavaScript
async function waitForAttestation(sourceDomain, transactionHash) {
  const pollInterval = 5000; // Poll every 5 seconds
  const maxWaitTime = 1200000; // 20 minutes maximum
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitTime) {
    try {
      const response = await fetch(
        `https://iris-api-sandbox.circle.com/v2/messages/${sourceDomain}?transactionHash=${transactionHash}`,
      );

      // 404 is expected while waiting - continue polling
      if (response.status === 404) {
        console.log("Attestation not yet available (404), waiting...");
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
        continue;
      }

      // Rate limited - wait before retrying
      if (response.status === 429) {
        console.log("Rate limited, waiting 5 minutes...");
        await new Promise((resolve) => setTimeout(resolve, 300000));
        continue;
      }

      if (!response.ok) {
        throw new Error(`Unexpected HTTP error: ${response.status}`);
      }

      const data = await response.json();

      // Empty messages array - transaction found but not processed
      if (!data.messages || data.messages.length === 0) {
        console.log("Transaction found, awaiting processing...");
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
        continue;
      }

      const message = data.messages[0];

      // Attestation complete
      if (message.status === "complete" && message.attestation) {
        console.log("Attestation retrieved successfully!");
        return {
          message: message.message,
          attestation: message.attestation,
          decodedMessage: message.decodedMessage,
        };
      }

      // Still pending
      console.log(`Attestation status: ${message.status}`);
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    } catch (error) {
      console.error("Error fetching attestation:", error.message);
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
  }

  throw new Error("Attestation not received within maximum wait time");
}

Avoid rate limiting

The attestation service limits requests to 35 per second. If you exceed this limit, the service blocks all API requests for 5 minutes and returns HTTP 429. Best practices:
  • Use a poll interval of at least 5 seconds
  • Implement exponential back-off for repeated 429 responses
  • Don’t poll from multiple clients for the same transaction

Troubleshooting checklist

If your attestation isn’t available after the expected wait time:
1

Verify the burn transaction

Check the source blockchain’s block explorer to confirm:
  • The transaction succeeded (not reverted)
  • The transaction is included in a mined block
  • Sufficient blocks have been confirmed since the transaction
2

Confirm request parameters

Verify you’re using the correct:
  • Source domain ID (see Supported Chains and Domains)
  • Transaction hash (full hash, including 0x prefix for EVM chains)
  • API environment (sandbox vs. production)
3

Check finality requirements

Standard Transfers require full finality. For some blockchains, this can take significantly longer than Fast Transfers. Check Finality and Block Confirmations for expected times.
4

Verify API connectivity

Test that you can reach the API:
Shell
curl --request GET \
  --url 'https://iris-api-sandbox.circle.com/v2/publicKeys' \
  --header 'Accept: application/json'
If this fails, check your network connectivity and firewall settings.