Skip to main content
Several AgriBackup endpoints return 202 Accepted rather than an immediate result. Understanding why — and how to work with the asynchronous job and webhook system — is essential before you build a production integration.

Why 202 Accepted?

Two operations at the heart of the compliance pipeline cannot complete in a single HTTP round-trip: Satellite deforestation screening. When you submit GeoJSON polygons, AgriBackup dispatches each feature to the Copernicus Data Space Ecosystem (CDSE) for analysis against Sentinel-2 imagery. Depending on imagery availability and the number of features, screening can take seconds to minutes. Holding the HTTP connection open for that duration is impractical for enterprise batch volumes. Hedera DLT anchoring. Anchoring a batch, shipment link, or DDS to the Hedera Hashgraph requires waiting for network consensus. While Hedera’s finality is fast (typically 3–5 seconds), the API cannot return a 200 OK before the transaction ID is confirmed on-chain — doing so would allow you to build compliance logic on a transaction that might never reach consensus. The endpoints that trigger one or both of these operations return 202 Accepted immediately, with a job ID you use to track progress:
EndpointAsync reason
POST /api/v1/enterprise/eudr/polygonsSatellite screening + Hedera anchoring
POST /api/v1/enterprise/eudr/batchesHedera anchoring + Copernicus risk assessment
POST /api/v1/enterprise/eudr/shipments/linkHedera smart contract execution
POST /api/v1/enterprise/eudr/declarations/ddsHedera land tenure anchoring + TRACES NT submission
POST /api/v1/enterprise/eudr/reports/archiveBulk ZIP compilation

The 202 response payload

Every async endpoint returns a JobAcceptedResponse body and a Location header:
{
  "jobId": "job_998877",
  "status": "PENDING",
  "estimatedDurationSec": 3,
  "createdAt": "2026-06-15T12:00:00Z"
}
jobId
string
required
The unique ID you use to poll job status. Example: job_998877
status
string
required
Always PENDING at acceptance time.
estimatedDurationSec
integer
required
Advisory estimate of how long before the job completes or reaches the next phase.
createdAt
string
required
ISO-8601 timestamp of when the job was accepted.
The Location response header points directly to the job status URL, for example:
Location: /api/v1/enterprise/eudr/jobs/job_998877

Polling the job endpoint

If you prefer polling over webhooks, call GET /api/v1/enterprise/eudr/jobs/{jobId} repeatedly. The response includes a Retry-After header when the job is still in progress, telling you how many seconds to wait before the next poll:
{
  "jobId": "job_998877",
  "organizationId": "org_test_tier1_alpha",
  "phase": "ANCHORING",
  "totalRecords": 10,
  "processedRecords": 7,
  "failedRecords": 0,
  "compliantUnits": 7,
  "highRiskUnits": 0,
  "merkleRootHash": "0xabc123...",
  "hederaTransactionId": "0.0.8713513@1713583200-000000000",
  "errors": [],
  "startedAt": 1713583200000,
  "completedAt": null
}

Job phases

Jobs move through the following phases in order:
PhaseDescription
IMPORTINGRecords are being ingested and validated
ANALYZINGCopernicus satellite screening is running
ANCHORINGHedera DLT anchoring is in progress
GENERATING_CERTSDDS XML or archive ZIP is being compiled
COMPLETEDJob finished successfully
FAILEDJob terminated with errors — check the errors array
Respect the Retry-After header. Polling more aggressively than its value recommends counts against your rate limit quota without providing useful information.

Webhooks

Webhooks are the recommended way to react to async completion events in production. Rather than polling, you register an HTTPS endpoint and AgriBackup delivers a POST payload to it the moment each event fires.

Registering a webhook

Call POST /api/v1/enterprise/eudr/webhooks with the URL you want to receive events and the event types you want to subscribe to:
{
  "targetUrl": "https://erp.example.com/agribackup-webhooks",
  "eventTypes": [
    "shipment.linked",
    "polygon.verified",
    "batch.risk_assessed",
    "dds.submitted",
    "dds.validated",
    "dds.rejected",
    "job.failed",
    "report.ready"
  ]
}
AgriBackup returns a signingSecret at registration time. Store this secret securely — you use it to verify every incoming payload.

Webhook event types

EventFired when
polygon.verifiedSatellite screening completes and the polygon receives a compliance verdict
batch.risk_assessedA batch finishes Hedera anchoring and risk assessment
shipment.linkedThe Hedera smart contract confirms a batch-to-shipment link
dds.submittedA DDS has been sent to TRACES NT
dds.validatedTRACES NT confirms the DDS is valid
dds.rejectedTRACES NT rejects the DDS — the payload body contains the rejection reason
job.failedAny async job terminates in the FAILED phase
report.readyA bulk archival ZIP is ready for download

Verifying webhook payloads

AgriBackup signs every outbound webhook with HMAC-SHA256 using the signing secret you received at registration. The signature is sent in the X-AgriBackup-Signature header as a hex-encoded digest of the raw request body. Verify it before processing the payload:
import hmac
import hashlib

def verify_webhook(raw_body: bytes, signature_header: str, signing_secret: str) -> bool:
    expected = hmac.new(
        signing_secret.encode('utf-8'),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    # Use constant-time comparison to prevent timing attacks
    return hmac.compare_digest(expected, signature_header)
import crypto from 'crypto';

function verifyWebhook(rawBody, signatureHeader, signingSecret) {
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(rawBody)
    .digest('hex');
  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;

public boolean verifyWebhook(byte[] rawBody, String signatureHeader, String signingSecret)
    throws Exception {
  Mac mac = Mac.getInstance("HmacSHA256");
  mac.init(new SecretKeySpec(signingSecret.getBytes("UTF-8"), "HmacSHA256"));
  String expected = bytesToHex(mac.doFinal(rawBody));
  // Constant-time comparison
  return MessageDigest.isEqual(expected.getBytes(), signatureHeader.getBytes());
}
Always verify the X-AgriBackup-Signature header before acting on a webhook payload. Processing unsigned or tampered payloads could cause you to act on fraudulent compliance events.

Dead-letter queue

If AgriBackup cannot deliver a webhook after repeated retries (for example, your endpoint is down), the event moves to the dead-letter queue (DLQ). Retrieve permanently failed events with GET /api/v1/enterprise/eudr/webhooks/dlq and replay individual or bulk events back to your primary endpoint with the replay endpoints.

Async flow sequence

The following diagram illustrates the full asynchronous flow for polygon ingestion:
Your ERP                    AgriBackup API           Copernicus CDSE      Hedera Hashgraph
   │                              │                        │                    │
   │─── POST /polygons ──────────►│                        │                    │
   │◄── 202 Accepted (jobId) ─────│                        │                    │
   │                              │─── Screen polygons ───►│                    │
   │─── GET /jobs/{jobId} ───────►│  (phase: ANALYZING)    │                    │
   │◄── 200 phase: ANALYZING ─────│                        │                    │
   │                              │◄── Verdicts ───────────│                    │
   │                              │─── Anchor on Hedera ───────────────────────►│
   │                              │◄── Consensus tx ID ────────────────────────│
   │                              │  (phase: COMPLETED)    │                    │
   │◄══ POST polygon.verified ════│                        │                    │
   │    (webhook delivery)        │                        │                    │
  1. You POST the polygon payload and immediately receive 202 Accepted with a jobId.
  2. AgriBackup queues satellite screening with Copernicus CDSE.
  3. You can optionally poll GET /jobs/{jobId} — the Retry-After header guides your polling interval.
  4. Once screening completes, AgriBackup anchors the results on Hedera and waits for consensus.
  5. On consensus, the job moves to COMPLETED and AgriBackup delivers the polygon.verified webhook to your registered endpoint.

Idempotency keys

Mutating endpoints that are either asynchronous or expensive to retry accept an Idempotency-Key header. Pass a unique string per logical operation — a UUID, a reference from your ERP, or any value that uniquely identifies the request:
curl -X POST https://live.agribackup.com/api/v1/enterprise/eudr/polygons \
  -H "X-API-Key: sk_live_YOUR_API_KEY" \
  -H "Idempotency-Key: tx_erp_ref_20260715_001" \
  -H "Content-Type: application/json" \
  -d '{"features": [...]}'
If your network times out before you receive the 202 Accepted, resend the exact same request with the same Idempotency-Key. AgriBackup returns the original 202 response instead of creating a duplicate job. This is safe to retry any number of times.
Idempotency keys are scoped to your organization and expire after 24 hours. Use a key that encodes enough context (for example, batch_BR-2026-991_ingest) to be meaningful in logs and support tickets.

When to always include an Idempotency-Key

  • POST /api/v1/enterprise/eudr/polygons — polygon ingestion
  • POST /api/v1/enterprise/eudr/batches — batch registration
  • POST /api/v1/enterprise/eudr/shipments/link — shipment linkage
  • POST /api/v1/enterprise/eudr/declarations/dds — DDS generation
Omitting the key on these endpoints means a network-level retry will create a second job.