AWS Lambda webhook receiver for Sume: function URL and HMAC

Give Sume a Lambda function URL with auth type NONE, decode the event body, check the sume-v1 HMAC, and answer 204 inside the 10-second window.

5 min readSume
All posts

To use AWS Lambda as a Sume webhook receiver, create a function URL with auth type NONE, pass it as communication.webhook_url when you start a run, and check Sume's signature in the handler: decode event["body"] from base64 when isBase64Encoded is true, compute HMAC-SHA256 over <timestamp>.<raw_body>, and accept the delivery when any sume-v1= entry matches. Then answer 204 well inside Sume's 10-second attempt window.

AWS facts come from the Lambda Developer Guide: Invoking function URLs, Control access to function URLs, function timeout, and the webhook tutorial. Sume facts come from Run webhooks, Webhooks, and Runs and results. All were read on 2026-09-27. Sume has no Lambda integration or package: the handler below is plain Python. Signing, retries, and rotation in general are covered in Signed webhooks for Sume video runs.

Which function URL settings does a Sume webhook need?

Five settings decide whether a delivery reaches your code in time. Sume refuses localhost, private-network, and non-HTTPS webhook URLs; a function URL is none of those, and Sume's webhook URL rules list the other refusals.

From AWS's Control access to function URLs, Creating and managing function URLs, and timeout pages and Sume's Run webhooks and Runs and results pages, read 2026-09-27.
SettingFor SumeWhy
Auth typeNONEAWS_IAM requires SigV4-signed requests. Sume signs deliveries with its own HMAC headers instead, which the handler checks.
Resource-based policyAllow lambda:InvokeFunctionUrl and lambda:InvokeFunctionNeeded even with NONE; without it, callers get 403 Forbidden. The console and AWS SAM create it; with the AWS CLI or CloudFormation you add it yourself.
TimeoutUp to 10 secondsLambda's default is 3 seconds and the maximum is 900. Sume gives each attempt 10 seconds, and a slow answer is retried.
Reserved concurrencyRoom for concurrent deliveriesAbove it, the URL answers 429. Sume retries, and run webhooks honor your Retry-After on a 429.
The URLhttps://<url-id>.lambda-url.<region>.on.awsHTTPS on the default port; current Sume code refuses a URL with a non-default port. The URL never changes, and a deleted one cannot be recovered.

How do I read the raw body from a Lambda event?

Lambda maps each request to an event in payload format version 2.0: headers holds the request headers as key-value pairs, and body is a string, base64-encoded when the request's content type is binary, which isBase64Encoded reports. Decode first, verify those bytes, then parse JSON. The check follows Python webhook HMAC verification, adapted to the event: it lowercases header names, refuses an empty secret, and compares every sume-v1= entry in constant time. It reads the secret from an environment variable to stay short; AWS recommends Secrets Manager instead for API keys and other sensitive values.

On a Node.js runtime, verifyWebhook from @sume-com/sdk runs the same check: it needs Node 18 or later, accepts the decoded bytes as a typed array, and reads event.headers as a plain object.

import base64, hashlib, hmac, json, os, time

SECRET = os.environ["SUME_COM_WEBHOOK_SIGNING_SECRET"].encode()

def lambda_handler(event, context):
    body = event.get("body") or ""
    raw = base64.b64decode(body) if event.get("isBase64Encoded") else body.encode()
    headers = {k.lower(): v for k, v in (event.get("headers") or {}).items()}
    ts = headers.get("x-sume-webhook-timestamp", "")
    if not SECRET or not ts.isdecimal() or abs(time.time() - int(ts)) > 300:
        return {"statusCode": 401}  # no secret, or outside the 5-minute window
    digest = hmac.new(SECRET, f"{int(ts)}.".encode() + raw, hashlib.sha256).hexdigest()
    expected = f"sume-v1={digest}".encode()
    matched = False
    for entry in headers.get("x-sume-webhook-signature", "").split(","):
        matched = hmac.compare_digest(entry.strip().encode(), expected) or matched
    if not matched:
        return {"statusCode": 401}
    event_body = json.loads(raw)
    record_once(event_body)  # your queue or table, keyed on request_id or job_id
    return {"statusCode": 204}

What changes from AWS's webhook tutorial?

AWS's own tutorial builds a webhook endpoint on a function URL with auth type NONE and an HMAC check. That check does not match Sume's scheme, so change four things:

  • What is signed. The tutorial HMACs the body alone and reads one x-webhook-signature header. Sume signs <timestamp>.<raw_body> and sends x-sume-webhook-timestamp with x-sume-webhook-signature.
  • Which bytes. The tutorial hashes event['body'] as it arrives. Decode it first when isBase64Encoded is true.
  • The comparison. The tutorial's Node.js version compares with ===. Compare in constant time, as its Python version does with hmac.compare_digest, which Python documents as designed to prevent timing analysis.
  • The header format. For 24 hours after a secret rotation, Sume sends two comma-separated sume-v1= entries, newest first. Accept a match on any entry: a check that compares the whole header fails every delivery in that window.

Why not wait for the video inside the Lambda function?

Because a run can outlast the invocation. A Lambda timeout is at most 900 seconds (15 minutes), while long-form host video typically finishes in 15 to 30 minutes, and Sume force-finalizes a run as failed at most 90 minutes after created_at. A function that times out while waiting does not stop the run: it keeps running and billing.

So start the run from any service with the function URL as communication.webhook_url, store data.id, and return. Generation jobs take the same URL as webhook_url or its alias callback_url, and one verifier covers run and job deliveries.

What should the handler do once the signature checks out?

Record the event where other code can pick it up, answer 204, and do the slow work outside the handler: Sume retries an answer that misses its 10-second attempt window. Dedupe on request_id for runs and job_id for jobs, and keep a read of result_url as the backup; Signed webhooks for Sume video runs covers the other delivery rules.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume