# Webhook audit trail: agent workflows को शुरू से अंत तक trace करें

जो agent action किसी asynchronous workflow को शुरू करता है, उसकी दो histories होती हैं: भेजी गई request और वापस आया event। Teams अक्सर पहली history इतनी अच्छी तरह log करती हैं कि वे पूछ सकें, «क्या agent ने API call की?» और दूसरी history इतनी अच्छी तरह कि पूछ सकें, «क्या हमारे receiver को webhook मिला?» फिर incident के दौरान पता चलता है कि कोई साबित नहीं कर सकता कि दोनों records एक ही काम से जुड़े हैं।

Webhook audit trail को intent, authorization, outbound delivery, remote acknowledgement, incoming receipt, verification और उस business state को जोड़ना चाहिए जिसे आपने स्वीकार किया है। 200 response को record का अंत मानने से payment, deployment, ticket या access change तीन दिन बाद समझाना असंभव हो सकता है।

## Webhook audit trail दो अलग तथ्य दर्ज करता है

Webhook audit trail में action request और event notification का अंतर सुरक्षित रहना चाहिए, क्योंकि दोनों अलग सवालों के जवाब देते हैं। Outbound पक्ष बताता है कि agent ने remote service से क्या करने को कहा। Inbound पक्ष बताता है कि बाद में किसी sender ने क्या होने का दावा किया।

ये तथ्य एक ही workflow से जुड़े हो सकते हैं, लेकिन इनके failure modes अलग होते हैं। Agent command भेजकर connection खो सकता है और उसे फिर भेज सकता है। Remote service command स्वीकार करके कुछ मिनट बाद उसे चला सकती है और दो समान callbacks भेज सकती है। आपका receiver पहला callback verify कर ले, लेकिन resulting state commit करने से पहले fail हो जाए। Application log की एक साफ-सुथरी पंक्ति इस पूरी श्रृंखला को नहीं समझा सकती।

इन systems की समीक्षा करते समय मैं छह record types रखता हूँ:

- Action record agent run, human authorization, requested operation और intended target की पहचान करता है।
- Outbound attempt record हर HTTP transmission की पहचान करता है, जिसमें request digest और मिला हुआ response शामिल है।
- Remote reference record provider द्वारा लौटाई गई ID, जैसे job या operation ID, सुरक्षित रखता है।
- Receipt record हर inbound HTTP delivery को business processing शुरू होने से पहले दर्ज करता है।
- Verification record ठीक-ठीक बताता है कि receiver ने delivery स्वीकार, अस्वीकार या quarantine क्यों की।
- State transition record बताता है कि verified event process करने के बाद workflow में क्या बदलाव हुआ।

Attempt को action के साथ merge न करें। एक action कई attempts बना सकता है। Receipt को event के साथ भी merge न करें। एक provider event आपके endpoint तक कई बार पहुँच सकता है। यह अंतर तब महत्वपूर्ण हो जाता है जब engineer को बताना हो कि दूसरा deployment agent के retry से आया, HTTP client के retry से या provider द्वारा उसी event को दोबारा भेजने से।

RFC 9110 में POST को जानबूझकर व्यापक रूप से परिभाषित किया गया है: target resource representation को अपने semantics के अनुसार process करता है। इसलिए 202 का सामान्य अर्थ «बाद के काम के लिए स्वीकार» होता है, और 200 का अर्थ भी केवल इतना है कि endpoint ने अपनी request handling पूरी कर ली। यह remote business outcome का प्रमाण नहीं है। अगर provider अलग operation status endpoint या callback देता है, तो वही बाद का evidence outcome का आधार है।

अच्छा record reviewer को timestamps से अनुमान लगाए बिना कहानी क्रम से पढ़ने देता है:

```text
agent session sess_7c1e authorized action act_01
act_01 created outbound attempt out_01 with idempotency ref idem_44
remote service accepted out_01 and returned operation op_903
receiver accepted delivery rcp_01 for provider event evt_775
rcp_01 verified its signature and linked evt_775 to op_903
workflow wf_18 moved from pending to completed
```

यह एक single status field नहीं, assertions की श्रृंखला है। हर assertion का अपना source और time होना चाहिए।

## Correlation के लिए एक से अधिक identifier चाहिए

एक अकेला correlation ID webhook tracing की समस्या हल नहीं करता, क्योंकि अलग parties अलग scopes के लिए IDs बनाती हैं। कुछ IDs रखें, उनकी ownership स्पष्ट करें और उनके relationships रिकॉर्ड करें।

सबसे पहले internal action ID बनाएँ। इसे किसी भी network call से पहले बनाकर agent session, requested operation, authorization decision और immutable audit entry से जोड़ें। यह ID जवाब देती है: «किस agent instruction से यह काम शुरू हुआ?» Client retry करने पर यह बदलनी नहीं चाहिए।

हर बार जब आपका HTTP client transmit करे, outbound attempt ID बनाएँ। यह बताती है: «किस wire attempt से यह response या error मिला?» जब remote API support करे, idempotency reference भी रखें। इसका अर्थ है कि repeated submissions एक logical remote operation से जुड़नी चाहिए। इससे यह पता नहीं चलता कि कोई खास HTTP attempt server तक पहुँची या नहीं।

Remote service operation ID लौटाए तो उसे तुरंत उस attempt के साथ save करें जिसने वह ID प्राप्त की। Request में client reference या metadata field की अनुमति हो, तो provider के callbacks या status responses में उसके लौटने की पुष्टि के बाद उसमें अपना action ID रखें। Reference field में secret, employee name या पूरा prompt कभी न डालें। ये fields vendor consoles, support tickets और event payloads में दिखाई दे सकते हैं।

Incoming callbacks दो और IDs जोड़ते हैं: provider event ID और आपकी receipt ID। Provider event ID उस sender के लिए deduplication में मदद करती है। Receipt ID आपके infrastructure द्वारा प्राप्त की गई ठीक उसी HTTP delivery की पहचान करती है, जिसमें headers, source address यदि आप रखते हों, raw-body digest और verification result शामिल हैं।

Relationship table इस तरह दिखनी चाहिए:

| Identifier | Created by | Stable across retries? | Answers |
|---|---|---:|---|
| Action ID | Your action service | Yes | Which agent request initiated work? |
| Attempt ID | Your HTTP client | No | Which transmission produced this result? |
| Idempotency reference | Your action service | Yes | Which submissions mean the same remote command? |
| Remote operation ID | Provider | Usually | Which remote job or object changed? |
| Provider event ID | Provider | Yes for one event | Which callback should be deduplicated? |
| Receipt ID | Your receiver | No | Which delivery did we receive? |

CloudEvents यहाँ उपयोगी है, भले provider CloudEvents न भेजता हो। इसका specification `id`, `source`, `type`, `subject` और `time` को अलग रखता है। इससे बार-बार होने वाली गलती रुकती है: event ID को workflow ID समझ लेना। Event ID एक source के एक event की पहचान करती है। Workflow ID उस काम की पहचान करती है जिसे आप trace कर रहे हैं। दोनों एक ही remote object की ओर इशारा कर सकते हैं, लेकिन उनका अर्थ एक नहीं है।

अगर provider केवल object ID वाला callback payload देता है, तो link सावधानी से बनाएँ। Link को exact तभी कहें जब object ID आपके recorded outbound response से आई हो या authenticated status lookup से मिली हो। Email address, title text, amount या timestamp का match correlation जैसा दिख सकता है, लेकिन वह अनुमान है। उसे audit conclusion का आधार न बनाएँ।

## 2xx response और callback अलग सवालों का जवाब देते हैं

2xx response HTTP exchange को settle करता है। Verified callback remote state change को settle कर सकता है। आपके workflow को दोनों चाहिए और इनके बीच के अंतर को ईमानदारी से बताना चाहिए।

मान लें agent hosted build service से artifact publish करने को कहता है। Service 202 और operation ID लौटाती है। आपकी service request को accepted रिकॉर्ड करके इंतज़ार करती है। दस मिनट बाद callback बताता है कि downstream repository ने आवश्यक manifest अस्वीकार कर दिया, इसलिए publication fail हुई। अगर audit record 202 पर «success» हो गया था, तो अब वह provider के अपने evidence से विरोध करता है।

ऐसी states इस्तेमाल करें जो आपके पास मौजूद evidence का नाम बताती हों। उदाहरण के लिए:

1. `requested` का अर्थ है कि agent action authorization से गुजर चुका है और work item बन गया है।
2. `submitted` का अर्थ है कि कम से कम एक outbound attempt को acceptance response मिला है, या recoverable ambiguous result की जाँच बाकी है।
3. `confirmed` का अर्थ है कि verified callback या authenticated status response ने intended outcome स्थापित कर दिया है।
4. `failed` का अर्थ है कि authoritative evidence ने failure स्थापित कर दी है।
5. `unknown` का अर्थ है कि आप अभी तय नहीं कर सकते कि remote side ने काम किया या नहीं।

`unknown` state ज़रूरी है। Teams इसे पसंद नहीं करतीं क्योंकि dashboards कम सुंदर दिखते हैं। लेकिन मुझे छिपे हुए duplication से अधिक समस्या है। POST भेजने के बाद timeout होने पर delivery ambiguous होती है: remote system ने request पाई और process की हो सकती है, या उसने उसे देखा ही न हो। Idempotency mechanism के बिना retry करने पर दो remote operations बन सकती हैं। पहले attempt को «failed» कहना इसी गलती को बढ़ावा देता है।

Late callback भी अपने-आप सही नहीं हो जाता। मान लें agent initial command के बाद cancellation माँगता है और आपका internal workflow valid cancellation रिकॉर्ड करता है। बाद में आया completion callback बता सकता है कि cancellation लागू होने से पहले remote side पर क्या हुआ। उसे रखें, verify करें, link करें और conflict दर्ज करें। केवल इसलिए generic handler को terminal cancellation state overwrite न करने दें कि किसी enum में «completed» ऊपर है।

हर callback type के लिए transition rule लिखें। Payment approval, build completion, user-provisioned event और deletion confirmation के लिए एक ही transitions नहीं होने चाहिए। Rule में बताएं कि कौन-सी prior states transition की अनुमति देती हैं, handler को कौन-सा evidence चाहिए और conflict होने पर operator को निर्णय लेना है या नहीं।

## Receiver को parsing से पहले evidence सुरक्षित रखना चाहिए

Receiver को raw delivery capture, verification और deduplication side effect से पहले करनी चाहिए। पहले JSON parse करके केवल चुने हुए fields save करने से parser, schema या application code में बाद में गलती मिलने पर evidence नष्ट हो जाता है।

Receipt के समय protected event store में यह दर्ज करें:

- Receipt ID और server receipt timestamp।
- Request method, route, चुने हुए headers और ठीक raw body का cryptographic digest।
- अपेक्षित sender identity और लागू की गई verification scheme।
- Payload में provider event ID हो तो वह, साथ में parsed event type।
- Decision: accepted, duplicate, rejected या quarantined, और reason code।

Raw payloads उतने ही समय तक रखें जितना आपकी investigation और compliance needs उचित ठहराती हैं। दो payloads के समान होने का प्रमाण देने के लिए digest आमतौर पर पर्याप्त है। Body रखना हो तो उसे encrypt करें, access सीमित करें और ordinary application logs में copy न करें। Webhooks में अक्सर personal data, repository metadata, addresses और internal notes होते हैं। Payload leak करने वाला audit store liability है, evidence नहीं।

Signature verification उसी body पर होनी चाहिए जिसे sender ने sign किया था। JSON parse करके reformat करने वाली middleware और फिर reformatted bytes verify करने पर legitimate deliveries reject हो सकती हैं या inconsistent handling की गुंजाइश बन सकती है। Provider का verification document ध्यान से पढ़ें। कुछ schemes `timestamp + "." + raw_body` sign करती हैं, कुछ केवल raw body, और कुछ rotating public keys के साथ asymmetric signatures इस्तेमाल करती हैं।

केवल raw body sign करने वाली generic HMAC scheme के लिए यह command unmodified bytes से अपेक्षित digest का रूप दिखाती है:

```sh
printf '%s' "$RAW_BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET"
# SHA2-256(stdin)= 4d3c...hex digest...
```

यह diagnostic है, provider की exact canonical string का विकल्प नहीं। Provider timestamp या version prefix शामिल करता हो, तो generic command गलत result देगी। यह गलती इसलिए अक्सर होती है क्योंकि engineers sender के documented algorithm के बजाय सुविधाजनक approximation verify करते हैं।

Signature validity replay को नहीं रोकती। Sender signed timestamp देता हो तो measured clock skew की अनुमति के बाद narrow window से बाहर की deliveries reject करें। फिर downstream work शुरू करने से पहले provider event IDs को durable deduplication store में दर्ज करें। Event ID पर भरोसा न हो तो sender-scoped digest और उचित retention period से deduplicate करें, यह ध्यान रखते हुए कि दो वैध समान events के लिए special handling चाहिए हो सकती है।

Receipt decision durable बनाने के बाद ही HTTP response लौटाएँ। पहले success लौटाकर deduplication write से पहले crash होने पर sender retry कर सकता है और handler वही event दो बार process कर सकता है। Low-volume testing में यह bug छिपा रहता है और ठीक उस outage में दिखता है जब webhook traffic बढ़ जाता है।

## Retries दिखाते हैं कि records कहाँ अस्पष्ट हैं

Retries सामान्य व्यवहार हैं, edge case नहीं, और हर layer स्वतंत्र रूप से retry कर सकती है। Agent timeout के बाद retry करता है। HTTP library connection failure पर retry करती है। API provider callbacks retry करता है। Queue consumer failed handler को retry करता है। इन्हें «retry count: 3» में समेट देने वाला audit record किसी की मदद नहीं करता।

एक ऐसी failure पर ध्यान दें जो मुझे कई रूपों में दिखी है। Agent remote access record बनाने को कहता है। Client POST भेजता है और bytes machine से निकलने के बाद timeout हो जाता है। Remote service record बनाकर callback queue कर देती है। Agent framework timeout देखकर retry करता है। Action layer हर attempt पर नई idempotency reference बनाती है, इसलिए दूसरा request दूसरा record बना देता है। दोनों callbacks आ जाते हैं। Receiver केवल email address से match करता है, उन्हें duplicate मानता है और दूसरे को suppress कर देता है। Audit page एक completed request दिखाता है। Remote service में अब दो access records हैं।

हर component ने अपने हिसाब से plausible काम किया। System इसलिए fail हुआ क्योंकि उसने retry boundaries के पार logical command सुरक्षित नहीं रखा।

Sequence को इस तरह ठीक करें:

1. पहले outbound attempt से पहले action ID और idempotency reference एक बार बनाएँ।
2. हर attempt अलग रिकॉर्ड करें, जिसमें timeout और transport errors शामिल हों।
3. Ambiguity होने पर दूसरा command भेजने से पहले provider से idempotency reference या client reference के आधार पर पूछें।
4. हर authenticated callback को receipt की तरह स्वीकार करें, फिर केवल provider event ID deduplicate करें, remote object को नहीं।
5. Workflow complete घोषित करने से पहले recorded action के विरुद्ध remote objects की अपेक्षित संख्या मिलाएँ।

पहली idempotency check sender पर और दूसरी receiver पर होनी चाहिए। दोनों अलग समस्याएँ हल करती हैं। Sender idempotency duplicate remote commands रोकती है। Receiver deduplication एक remote event की repeated processing रोकती है। Teams अक्सर एक लगाकर समझती हैं कि दोनों मिल गए।

Business truth का sort order arrival time न बनाएँ। Providers events देर से या out of order भेज सकते हैं और आपकी queue processing रोक सकती है। कम से कम तीन times रखें: action service ने action कब बनाया, HTTP client ने attempt कब भेजी या response कब पाया, और receiver ने callback कब स्वीकार किया। Sender का claimed event time अलग रखें। Sender की clock उस sender का evidence है, आपकी clock नहीं।

## Authorization को asynchronous boundary पार करनी चाहिए

Agent action के लिए human approval action से जुड़ा होना चाहिए, बाद में आने वाले callback से नहीं। Callback remote work की जानकारी देता है। केवल इसलिए कि उसमें approved request जैसा correlation field है, उसे किसी नए privileged operation की authority नहीं मिलनी चाहिए।

यह तब महत्वपूर्ण है जब callbacks में URLs, object names, user-controlled metadata या ऐसे instructions आ सकते हों जिन्हें internal handler follow करता है। एक खराब design «job completed» event पाकर generic automation worker को broad credentials से result URL fetch करने या follow-up command चलाने देता है। Original agent approval job submit करने तक था, event में embedded खुले-ended actions तक नहीं।

Authorized action को concrete terms में रिकॉर्ड करें: actor session, requested endpoint या SSH command template, target scope, credential identity, approval result और approval time। हर outbound call को उस authorization record से जोड़ें। हर callback को verification और correlation के बाद ही action से जोड़ें। यह दिशा महत्वपूर्ण है। Incoming request को database में कोई भी सुविधाजनक prior approval खोजकर उधार नहीं लेना चाहिए।

Sallyport agent credentials को agent process से बाहर रखता है और agent runs तथा individual calls दोनों रिकॉर्ड करता है, इसलिए evidence का outbound भाग सुरक्षित रखना आसान होता है। फिर भी callback receiver को अपने receipt और workflow records चाहिए, क्योंकि HTTP action journal यह नहीं जान सकता कि remote system ने बाद में valid event भेजा या नहीं।

दोनों दिशाओं के लिए अलग credentials रखें। Outbound API call को authorize करने वाला credential सामान्यतः inbound signatures verify न करे, और inbound verification secret callback handler को मनमाने external APIs call करने की अनुमति न दे। Receiver route, dependency या log sink में समस्या होने पर अलग custody नुकसान सीमित करती है।

## Tamper evidence में calls के साथ joins भी शामिल हों

Outbound calls का append-only record उपयोगी है, लेकिन बाद में किए गए correlation decisions का प्रमाण नहीं देता। Operator या application bug किसी callback को गलत action से जोड़ सकता है, जबकि दोनों original HTTP records बदले न हों।

Correlation को first-class audit event बनाएँ। Event में action ID, receipt ID, link का आधार, निर्णय लेने वाला actor या process और इस्तेमाल किए गए fields का digest शामिल होना चाहिए। Explicit bases इस्तेमाल करें, जैसे `remote_operation_id_exact`, `client_reference_exact`, `authenticated_status_lookup` या `manual_review`। केवल «matched» न लिखें और investigator को अनुमान लगाने के लिए न छोड़ें।

Hash-chained log दिखा सकती है कि creation के बाद records बदले नहीं गए, बशर्ते आप log का append path सुरक्षित रखें और checkpoints बचाकर रखें। यह साबित नहीं कर सकती कि application ने उस समय सही decision लिया था। इस सीमा को साफ़-साफ़ बताना अच्छा है। Tamper evidence आपको system द्वारा दर्ज की गई स्थिर कहानी देता है, कमजोर correlation को तथ्य में नहीं बदलता।

Sallyport के encrypted, hash-chained audit log को `sp audit verify` से offline check किया जा सकता है, vault key के बिना भी। Action records के लिए ऐसी verification रखें और workflow store से relevant action तथा call identifiers तक comparable immutable reference भी सुरक्षित रखें।

High-consequence workflows के लिए reconciliation job जोड़ें जो तीन populations की तुलना करे: submitted actions, provider को ज्ञात remote operations और receiver द्वारा स्वीकार किए गए callbacks। Unpaired item के लिए exception record बनाएँ, उसे अपने-आप close न करें। Missing callback provider outage, गलत endpoint, signature rotation failure या workflow bug का संकेत हो सकता है। Optimism से पहले evidence चाहिए।

## Observability से investigator decision को फिर से देख सके

Investigator को किसी भी identifier से workflow reconstruct कर पाना चाहिए, बिना agent prompts या API secrets तक privileged access के। Integration ship करने से पहले search paths design करें।

Action ID से शुरू करने पर record में agent session, authorization, credential label, redaction के बाद request shape, सभी attempts, remote references, linked receipts और terminal workflow state दिखनी चाहिए। Provider event ID से शुरू करने पर उस event की हर delivery, verification results, deduplication outcome, linked operation और state changes दिखने चाहिए। Internal business object से शुरू करने पर ठीक वह evidence दिखना चाहिए जिसने उसे agent action से जोड़ा।

Single narrative string के बजाय structured fields रखें। उपयोगी event contract को schema review या log pipeline में इस्तेमाल किया जा सकता है:

```json
{
  "record_type": "callback_receipt",
  "receipt_id": "rcp_01J...",
  "received_at": "2025-03-08T22:14:31Z",
  "sender": "build-service",
  "provider_event_id": "evt_775",
  "event_type": "publication.finished",
  "raw_body_sha256": "4d3c...",
  "signature": {"scheme": "hmac-sha256", "result": "valid"},
  "correlation": {
    "action_id": "act_01J...",
    "remote_operation_id": "op_903",
    "basis": "remote_operation_id_exact"
  },
  "processing": {"deduplication": "new", "result": "completed"}
}
```

Body digest, verification result और correlation basis अस्पष्ट `status: success` से अधिक उपयोगी हैं। वे claims की जाँच संभव बनाते हैं। Provider callback पर विवाद करे तो preserved digest मिलाएँ। Engineer match पर सवाल उठाए तो basis देखें। Duplicate से side effects हुए हों तो देखें कि receiver ने work dispatch करने से पहले deduplication record लिखा था या नहीं।

Authorization headers, bearer tokens, private keys, signature secrets या पूरे credential-bearing URLs log न करें। Sensitive data वाले query values redact करें, लेकिन दो targets अलग पहचानने के लिए पर्याप्त request identity रखें। मैंने teams को URL इतना redact करते देखा है कि बाद में यह पता ही नहीं चला कि agent production endpoint से जुड़ा था या test endpoint से। Normalized host, route template, method और carefully scoped target identifier रखें।

## Agents के asynchronous calls से पहले trace बनाएँ

Agent को asynchronous work शुरू करने वाला action देने से पहले identifiers, receipt rules और state transitions तय करें। विवाद के बाद इन्हें जोड़ना महँगा पड़ता है, क्योंकि जो evidence कभी बना ही नहीं वह बाद में नहीं मिल सकता।

एक deliberate failure drill करें। Harmless test action submit करें, test environment अनुमति दे तो transmission के बाद client को timeout कराएँ, वही callback दोबारा भेजें, invalid signature वाला callback भेजें और workflow के terminal state में जाने के बाद valid callback deliver करें। जाँचें कि audit record हर outcome को बिना किसी human के memory से gaps भरने के समझाता है या नहीं।

अगर आपका system यह जवाब नहीं दे सकता कि «किस authorized action से यह callback आया, कौन-सा exact evidence है और हमने इसके साथ क्या किया?», तो उसमें अभी webhooks के लिए audit story नहीं है। उसमें केवल दो log sets हैं जो संयोग से एक ही clock share करते हैं।
