Alle Artikel
Shopware 65 min

Shopware 6 API Endpoint erstellen – Idempotent & sicher

Wenn du mit Shopware 6 arbeitest, kommst du früher oder später an den Punkt, wo die Standard-API nicht mehr ausreicht. Vielleicht brauchst du einen speziellen Endpoint für eine externe Integration, einen B2B-Workflow oder eine komplexe Business-Logik. In diesem Tutorial zeige ich dir, wie du einen Shopware 6 API Endpoint erstellst, der nicht nur funktioniert, sondern auch idempotent und sicher ist.

Warum Custom API Endpoints in Shopware 6?

Die Shopware 6 Store API und Admin API decken viele Use Cases ab. Aber manchmal brauchst du mehr Kontrolle:

  • Komplexe Geschäftslogik die mehrere Entities betrifft
  • Externe Integrationen mit spezifischen Anforderungen
  • Performance-Optimierungen durch gezielte Queries
  • Spezielle Validierungen die über Standard-Constraints hinausgehen

Ein selbst erstellter Shopware 6 API Endpoint gibt dir diese Kontrolle.

Die Grundstruktur eines Custom Endpoints

Shopware 6 nutzt das Symfony Routing-System. Ein API Endpoint besteht aus drei Komponenten:

  1. Route Definition (Annotation oder XML)
  2. Controller mit der Business-Logik
  3. Request/Response Handling

Hier ist ein einfaches Beispiel für einen Custom Endpoint:

<?php declare(strict_types=1);

namespace CustomPlugin\Core\Api;

use Shopware\Core\Framework\Context;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

#[Route(defaults: ['_routeScope' => ['api']])]
class CustomApiController extends AbstractController
{
    #[Route(
        path: '/api/_action/custom/process-order',
        name: 'api.action.custom.process.order',
        methods: ['POST']
    )]
    public function processOrder(Request $request, Context $context): JsonResponse
    {
        $data = json_decode($request->getContent(), true);
        
        // Business Logic hier
        
        return new JsonResponse([
            'success' => true,
            'data' => ['orderId' => $data['orderId']]
        ]);
    }
}

Die _routeScope mit Wert api sorgt dafür, dass der Endpoint unter /api erreichbar ist und die Standard-API-Authentifizierung nutzt.

Authentifizierung implementieren

Ein sicherer Shopware 6 API Endpoint braucht Authentifizierung. Shopware bietet zwei Hauptmethoden:

Integration Authentication (empfohlen)

Für Server-to-Server Kommunikation nutzt du Integration Keys:

#[Route(defaults: ['_routeScope' => ['api'], 'auth_required' => true])]
class CustomApiController extends AbstractController
{
    #[Route(
        path: '/api/_action/custom/sync-data',
        name: 'api.action.custom.sync.data',
        methods: ['POST']
    )]
    public function syncData(Request $request, Context $context): JsonResponse
    {
        // Context enthält bereits authentifizierte Integration
        $integrationId = $context->getSource()->getIntegrationId();
        
        // Zusätzliche Prüfung möglich
        if (!$integrationId) {
            return new JsonResponse(['error' => 'Unauthorized'], 401);
        }
        
        // Sichere Business Logic
        return new JsonResponse(['success' => true]);
    }
}

Custom Authentication

Für spezialisierte Use Cases kannst du eigene Authentication implementieren:

use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

private function validateApiKey(Request $request): void
{
    $apiKey = $request->headers->get('X-Api-Key');
    
    if (!$apiKey || !$this->isValidApiKey($apiKey)) {
        throw new UnauthorizedHttpException('API Key', 'Invalid API Key');
    }
}

#[Route(path: '/api/_action/custom/webhook', methods: ['POST'])]
public function webhook(Request $request, Context $context): JsonResponse
{
    $this->validateApiKey($request);
    
    // Geschützte Logic
    return new JsonResponse(['received' => true]);
}

Idempotenz implementieren

Idempotenz bedeutet: Mehrfache identische Requests haben denselben Effekt wie ein einzelner Request. Das ist kritisch für APIs, die über unzuverlässige Netzwerke laufen.

Idempotency Keys nutzen

use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Uuid\Uuid;

class IdempotentApiController extends AbstractController
{
    public function __construct(
        private readonly EntityRepository $customLogRepository
    ) {}
    
    #[Route(path: '/api/_action/custom/create-resource', methods: ['POST'])]
    public function createResource(Request $request, Context $context): JsonResponse
    {
        $idempotencyKey = $request->headers->get('Idempotency-Key');
        
        if (!$idempotencyKey) {
            return new JsonResponse(['error' => 'Idempotency-Key required'], 400);
        }
        
        // Prüfen ob Request bereits verarbeitet wurde
        $existingResult = $this->checkIdempotencyKey($idempotencyKey, $context);
        
        if ($existingResult) {
            return new JsonResponse($existingResult, 200);
        }
        
        // Neue Resource erstellen
        $resourceId = Uuid::randomHex();
        $result = [
            'success' => true,
            'resourceId' => $resourceId
        ];
        
        // Ergebnis mit Idempotency Key speichern
        $this->storeIdempotencyResult($idempotencyKey, $result, $context);
        
        return new JsonResponse($result, 201);
    }
    
    private function checkIdempotencyKey(string $key, Context $context): ?array
    {
        $criteria = new \Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria();
        $criteria->addFilter(new \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter('idempotencyKey', $key));
        
        $result = $this->customLogRepository->search($criteria, $context)->first();
        
        return $result ? json_decode($result->get('result'), true) : null;
    }
}

Error Handling Best Practices

Ein professioneller Shopware 6 API Endpoint liefert konsistente, hilfreiche Fehlermeldungen:

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

#[Route(path: '/api/_action/custom/validate-data', methods: ['POST'])]
public function validateData(Request $request, Context $context): JsonResponse
{
    try {
        $data = json_decode($request->getContent(), true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new BadRequestHttpException('Invalid JSON payload');
        }
        
        // Validierung
        $errors = $this->validatePayload($data);
        
        if (!empty($errors)) {
            return new JsonResponse([
                'errors' => $errors
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        
        // Erfolgreiche Verarbeitung
        return new JsonResponse(['success' => true]);
        
    } catch (BadRequestHttpException $e) {
        return new JsonResponse([
            'error' => $e->getMessage()
        ], Response::HTTP_BAD_REQUEST);
        
    } catch (\Exception $e) {
        // Logging nicht vergessen
        return new JsonResponse([
            'error' => 'Internal server error'
        ], Response::HTTP_INTERNAL_SERVER_ERROR);
    }
}

private function validatePayload(array $data): array
{
    $errors = [];
    
    if (!isset($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Invalid email address';
    }
    
    if (!isset($data['amount']) || $data['amount'] <= 0) {
        $errors[] = 'Amount must be greater than zero';
    }
    
    return $errors;
}

Rate Limiting hinzufügen

Schütze deinen Shopware 6 API Endpoint vor Missbrauch:

use Symfony\Component\RateLimiter\RateLimiterFactory;

class RateLimitedApiController extends AbstractController
{
    public function __construct(
        private readonly RateLimiterFactory $apiLimiter
    ) {}
    
    #[Route(path: '/api/_action/custom/public-endpoint', methods: ['POST'])]
    public function publicEndpoint(Request $request, Context $context): JsonResponse
    {
        $limiter = $this->apiLimiter->create($request->getClientIp());
        
        if (!$limiter->consume(1)->isAccepted()) {
            return new JsonResponse([
                'error' => 'Rate limit exceeded'
            ], 429);
        }
        
        // Endpoint Logic
        return new JsonResponse(['success' => true]);
    }
}

Die Rate Limiter Configuration kommt in services.xml:

<service id="api_rate_limiter" class="Symfony\Component\RateLimiter\RateLimiterFactory">
    <argument type="collection">
        <argument key="policy">token_bucket</argument>
        <argument key="limit">100</argument>
        <argument key="rate">
            <argument key="interval">1 minute</argument>
        </argument>
    </argument>
</service>

Testing deines API Endpoints

Teste deinen Shopware 6 API Endpoint gründlich:

# Mit Integration Token
curl -X POST https://your-shop.com/api/_action/custom/process-order \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_INTEGRATION_TOKEN" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"orderId": "01234567890abcdef"}'

# Idempotenz testen - gleicher Request sollte gleiches Ergebnis liefern
curl -X POST https://your-shop.com/api/_action/custom/process-order \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_INTEGRATION_TOKEN" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"orderId": "01234567890abcdef"}'

Performance-Tipps

  1. Nutze DAL EntityRepository effizient – vermeide N+1 Queries durch Associations
  2. Cache wo möglich – besonders bei read-heavy Endpoints
  3. Async Processing – für zeitintensive Operations Message Queue nutzen
  4. Pagination – bei List-Endpoints immer Pagination implementieren
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;

$criteria = new Criteria();
$criteria->setLimit(50);
$criteria->setOffset((int)$request->query->get('page', 0) * 50);
$criteria->addAssociation('customer');

$results = $this->repository->search($criteria, $context);

Fazit

Ein gut implementierter Shopware 6 API Endpoint ist sicher, idempotent und performant. Die wichtigsten Takeaways:

  • Nutze Shopware's eingebaute Authentifizierung
  • Implementiere Idempotenz mit Idempotency Keys
  • Konsistentes Error Handling
  • Rate Limiting für öffentliche Endpoints
  • Gründliches Testing

Du arbeitest an einem Shopware 6 API-Integration Projekt? Ich helfe dir gerne bei der Entwicklung robuster, skalierbarer API Endpoints.

Shopware 6 API EndpointShopware 6FreelancerWebentwicklungDüsseldorf