Configuration & Operations
Environment
The pipeline is off by default so the app boots locally without AWS/Temporal. Enable it with these environment variables on admin-api:
# Fanout transport (SNS -> SQS)
WEBHOOK_SQS_ENABLED=true
WEBHOOK_SNS_TOPIC_ARN=arn:aws:sns:<region>:<account>:webhook-events
WEBHOOK_SQS_QUEUE_NAME=webhook-fanout
AWS_REGION=ap-south-1 # AWS creds via the default provider chain
# Temporal Cloud (API-key auth)
TEMPORAL_START_WORKERS=true
TEMPORAL_TARGET=<region>.<cloud>.api.temporal.io:7233 # API-key REGIONAL endpoint
TEMPORAL_NAMESPACE=<namespace_id>.<account_id>
TEMPORAL_API_KEY=<api-key>
TEMPORAL_ENABLE_HTTPS=true
With API-key auth you must use the regional endpoint <region>.<cloud>.api.temporal.io:7233. The per-namespace …tmprl.cloud:7233 host is mTLS-only and rejects API keys with a certificate_required TLS alert.
AWS Resources
The SNS topic and SQS queue (plus a dead-letter queue) must exist before enabling the consumer. The runtime IAM user needs sns:Publish on the topic and ReceiveMessage / DeleteMessage / GetQueueUrl / GetQueueAttributes / ChangeMessageVisibility on the queue (no CreateQueue once the queue exists).
REGION=ap-south-1
# DLQ
DLQ_URL=$(aws sqs create-queue --region $REGION --queue-name webhook-fanout-dlq --query QueueUrl --output text)
DLQ_ARN=$(aws sqs get-queue-attributes --region $REGION --queue-url "$DLQ_URL" --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
# Main queue with redrive to the DLQ
QUEUE_URL=$(aws sqs create-queue --region $REGION --queue-name webhook-fanout \
--attributes '{"VisibilityTimeout":"60","RedrivePolicy":"{\"deadLetterTargetArn\":\"'"$DLQ_ARN"'\",\"maxReceiveCount\":\"5\"}"}' \
--query QueueUrl --output text)
QUEUE_ARN=$(aws sqs get-queue-attributes --region $REGION --queue-url "$QUEUE_URL" --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
# Topic + subscription (raw delivery so the SQS body is the message JSON)
TOPIC_ARN=$(aws sns create-topic --region $REGION --name webhook-events --query TopicArn --output text)
aws sqs set-queue-attributes --region $REGION --queue-url "$QUEUE_URL" --attributes \
'{"Policy":"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":\"sqs:SendMessage\",\"Resource\":\"'"$QUEUE_ARN"'\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"'"$TOPIC_ARN"'\"}}}]}"}'
aws sns subscribe --region $REGION --topic-arn "$TOPIC_ARN" --protocol sqs \
--notification-endpoint "$QUEUE_ARN" --attributes RawMessageDelivery=true
Admin API
All admin routes are under /api/v1/admin/webhooks (also mirrored at /api/v1/pms/webhooks) and require an admin JWT.
Inbound credentials
POST /api/v1/admin/webhooks/credentials
{ "source": "juspay", "username": "elivaas", "password": "s3cret", "active": true }
The password is stored bcrypt-hashed and used to verify the Authorization: Basic header on incoming calls.
Downstream endpoints
POST /api/v1/admin/webhooks/endpoints
{
"source": "juspay",
"eventType": "payment.success", // omit / null = all events for the source
"targetUrl": "https://partner.example.com/hook",
"httpMethod": "POST",
"authType": "HMAC", // NONE | BASIC | BEARER | HMAC
"signingSecret": "…", // for HMAC; or authUsername/authSecret for BASIC/BEARER
"customHeaders": { "X-Tenant": "elivaas" },
"maxAttempts": 5,
"timeoutSeconds": 30,
"active": true
}
Other routes: GET /endpoints, PUT /endpoints/{id}, PUT /endpoints/{id}/active?active=, DELETE /endpoints/{id}, and the same shape for /credentials.
Inspection & replay
GET /api/v1/admin/webhooks/events?limit=50 # recent received events
GET /api/v1/admin/webhooks/events/{id}/deliveries # per-endpoint delivery status
GET /api/v1/admin/webhooks/deliveries/failed # failed deliveries across events
POST /api/v1/admin/webhooks/events/{id}/replay # re-run fanout (fresh workflow)
Receiving a webhook
The public receive endpoint is POST /api/webhooks/{source} (no JWT — authenticated by Basic Auth against the source's credential):
curl -u elivaas:s3cret -X POST https://api.elivaas.com/api/webhooks/juspay \
-H 'Content-Type: application/json' \
-d '{"event_name":"payment.success","order_id":"ord_1"}'
The response is 200 {"status":"ok","eventId":"whe_…","duplicate":"false"}. Watch the fanout in the Temporal Cloud UI (task queue WEBHOOKS) and the resulting webhook_delivery rows per active endpoint.