Deno edge function returning 500 on GC webhook payload

Stuck on handling webhook failures in my Deno Deploy function. Genesys sends a retry after a 500, but I need to push the failed event to a dead letter queue instead of crashing the function. Context: My handler parses the event and calls a database. If the DB is down, the function throws and returns 500. Question: How do I catch the error, return 200 to GC to stop retries, and asynchronously queue the payload for later processing?

I typically get around this by wrapping the database logic in a try-catch block within your Deno handler. This prevents unhandled exceptions from triggering the default 500 response that triggers Genesys Cloud retries. You must explicitly return a 200 OK status to the webhook endpoint to signal successful receipt, even if the downstream processing fails.

  1. Parse the incoming JSON payload from the request body.
  2. Attempt your database insertion inside a try block.
  3. In the catch block, send the payload to your dead letter queue (e.g., S3 or SQS).
  4. Return new Response(null, { status: 200 }) immediately after queuing the failure.

This pattern ensures idempotency and stops the retry loop. Genesys Cloud considers the webhook successful if it receives a 2xx status code. By handling the error internally and returning 200, you decouple the webhook acknowledgment from the actual data persistence. This is critical for high-throughput event streams where database latency might cause timeout errors.

try {
 await db.insert(JSON.parse(await req.text()));
} catch (err) {
 await deadLetterQueue.send({ error: err, payload: await req.text() });
}
return new Response(null, { status: 200 });

have you tried checking if your webhook endpoint is actually validating the signature before you even touch the db? i see a lot of people just swallowing errors to stop retries, but that creates a silent failure loop. if you return 200 on a bad signature or malformed payload, genesys stops retrying and you lose the data forever. in my laravel apps i use a middleware to verify the x-genesys-signature header using hmac sha256 before passing it to the controller. if it fails, return 401. if it passes but the db fails, then you can consider the 200/async queue pattern.

$expected = hash_hmac('sha256', $payload, $secret, true);
if (!hash_equals($expected, $provided_signature)) {
 return response('Invalid signature', 401);
}

don’t just catch and return 200 blindly. verify integrity first. otherwise you are filling your dead letter queue with garbage or missing real events because the signature check was skipped.

It depends, but generally… swallowing the error is fine if you are logging it, but you must return 200 immediately to stop the GC retry storm.

try { await db.insert(payload); } catch (e) { console.error(e); }
return new Response(null, { status: 200 });

You need to wrap the db call in try-catch and return 200 immediately. docs state “webhook endpoints must respond with 200 to acknowledge receipt.”

try { await db.insert(payload); } catch(e) { console.error(e); }
return new Response(null, { status: 200 });